Desain: Metode objek vs metode kelas terpisah yang mengambil Objek sebagai parameter?

14

Misalnya, apakah lebih baik melakukan:

Pdf pdf = new Pdf();
pdf.Print();

atau:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Contoh lain:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

atau:

Country m = new Country("Mexico");
Country us = new Country("US");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(us);
double mRatio = ds.GetDebtToGDPRatio(m);    

Perhatian saya dalam contoh terakhir adalah ada potensi statistik tanpa akhir (tapi katakan saja hanya 10) Anda mungkin ingin tahu tentang suatu negara; apakah mereka semua termasuk dalam objek negara?

misalnya

Country m = new Country("Mexico");
double ratio = m.GetGDPToMedianIncomeRatio();

Ini adalah rasio sederhana tetapi anggaplah statistik cukup rumit untuk menjamin suatu metode.

Di mana garis antara operasi yang intrinsik ke objek vs operasi yang dapat dilakukan pada objek tetapi bukan bagian dari itu?

Pengguna
sumber

Jawaban:

16

Mengambil contoh PDF Anda sebagai titik awal, mari kita lihat ini.

http://en.wikipedia.org/wiki/Single_responsibility_principle

Prinsip Tanggung Jawab Tunggal menyarankan bahwa suatu objek harus memiliki satu dan hanya satu tujuan. Ingatlah ini.

http://en.wikipedia.org/wiki/Separation_of_concerns

Prinsip Separation of Concerns memberi tahu kita bahwa kelas tidak boleh memiliki fungsi yang tumpang tindih.

Ketika Anda melihat dua ini, mereka menyarankan bahwa logika harus masuk kelas hanya jika masuk akal, hanya jika kelas yang bertanggung jawab untuk melakukan itu.

Sekarang, dalam contoh PDF Anda, pertanyaannya adalah, siapa yang bertanggung jawab untuk mencetak? Apa yang masuk akal?

Cuplikan kode pertama:

Pdf pdf = new Pdf();
pdf.Print();

Ini tidak bagus. Dokumen PDF tidak mencetak dirinya sendiri. Itu akan dicetak oleh ... ta da! .. sebuah printer. Jadi Anda potongan kode kedua jauh lebih baik:

Pdf pdf = new Pdf();
PdfPrinter printer = new PdfPrinter();
printer.Print(pdf);

Ini masuk akal. Printer Pdf mencetak dokumen pdf. Lebih baik lagi, printer tidak boleh menjadi printer PDF, atau printer foto. Seharusnya hanya printer yang mampu mencetak barang-barang yang dikirim kepadanya dengan kemampuan terbaiknya.

Pdf pdf = new Pdf();
Printer printer = new Printer();
printer.Print(pdf);

Jadi itu sederhana. Letakkan metode di mana mereka masuk akal. Jelas, tidak selalu sesederhana itu. Ambil statistik negara Anda misalnya:

Country m = new Country("Mexico");
double ratio = m.GetDebtToGDPRatio();

Kekhawatiran Anda adalah bahwa mungkin ada n jumlah statistik, dan bahwa mereka tidak harus berada dalam kelas Country. Itu benar. Namun, jika model Anda hanya membutuhkan statistik tertentu, contoh pemodelan ini mungkin sebenarnya baik-baik saja.

Dalam hal ini, Anda dapat mengatakan dengan cukup logis bahwa suatu negara harus dapat menghitung statistiknya sendiri, khusus untuk model Anda dan persyaratan yang ada.

Dan di situlah letak masalahnya: apa kebutuhan Anda? Persyaratan Anda akan mendorong cara Anda memodelkan dunia, konteks, di mana persyaratan ini harus dipenuhi.

Jika Anda memang memiliki banyak statistik, maka contoh kedua Anda lebih masuk akal:

Country m = new Country("Mexico");
DebtStatistics ds = new DebtStatistics();
double usRatio = ds.GetDebtToGDPRatio(m);

Lebih baik lagi, memiliki superclass abstrak atau antarmuka yang disebut Statistik yang mengambil negara sebagai parameter:

interface StatisticsCalculator // or a pure abstract class if doing C++
{
   double getStatistics(Country country); // or a pure virtual function if in C++
}

class DebtToGDPRatioStatisticsCalculator mengimplementasikan StatisticsCalculator ....

kelas BayiMortalityStatisticsCalculator mengimplementasikan StatisticsCalculator ...

Dan seterusnya dan seterusnya. Yang mengarah pada hal berikut: generalisasi, delegasi, abstraksi. Pengumpulan statistik akan didelegasikan ke contoh spesifik yang menggeneralisasi abstraksi tertentu (API pengumpulan statistik).

Saya tidak tahu apakah ini menjawab pertanyaan Anda 100%. Lagipula, kita tidak memiliki model yang sempurna yang didasarkan pada hukum yang tidak dapat diganggu gugat (seperti yang dilakukan orang-orang EE.) Yang dapat Anda lakukan hanyalah memasukkan hal-hal yang masuk akal. Dan itu keputusan teknis yang harus Anda ambil. Hal terbaik untuk dilakukan adalah benar-benar mengenal prinsip-prinsip OO (dan prinsip-prinsip pemodelan perangkat lunak yang baik pada umumnya.)

luis.espinal
sumber
1
+1 untuk Interface StatisticsCalculator (dan selanjutnya menggunakan Pola Strategi). Dan jawaban yang dipikirkan dengan seksama
edwardsmatt
3
tidak cukup waktu untuk mendekonstruksi ini secara menyeluruh pada saat ini, tetapi harus menunjukkan bahwa kelas Printer akan menjadi kelas Dewa dari waktu ke waktu, secara erat digabungkan ke semua jenis kelas dokumen. Pdf.Print akan lebih disukai - tetapi semuanya tergantung pada bagaimana Anda mendefinisikan 'tanggung jawab tunggal' ;-)
Steven A. Lowe
@Steve - apa yang Anda usulkan adalah ide yang mengerikan (memiliki print Pdf implement ()). Itu tidak mencerminkan bagaimana pencetakan diterapkan dalam kehidupan nyata. Setiap sistem operasi dan API pencetakan yang saya sadari menyediakan abstraksi untuk Printer. Lihatlah daftar printer di mesin XP / Vista Anda (atau di bawah / var / spool atau setara dengan * nix.) Setiap aplikasi membuat serial objek dokumen ke salah satu printernya. Tidak ada printer Word, atau Printer teks atau printer PDF. Hanya ada printer khusus untuk perangkat pencetakan dan tidak khusus untuk jenis dokumen.
luis.espinal
2
+1 Saya suka saya merenungkan apa yang Anda katakan .. @Steve dan luis: Saya pikir bagian yang hilang dari perdebatan tentang objek Tuhan adalah objek Printer generik harus menerima beberapa format standar seperti ASCII atau bitmap (walaupun pdf mungkin masuk akal juga) dan itu harus menjadi tanggung jawab beberapa kelas ke-3 untuk mengonversi jenis dokumen tertentu (mis. dokumen kata ms) ke salah satu format standar ini.
Pengguna
2
Sepertinya saya bahwa mungkin PDF harus dapat membuat sendiri ke antarmuka Canvas atau menjadi objek Gambar yang kemudian dapat diproses oleh objek Printer.
Winston Ewert
4

Saya pikir tidak ada yang lebih baik dari yang lain. Menggunakan pdf.Print () lebih ketat, tetapi memiliki kelas PdfPrinter mungkin lebih baik jika:

  • Anda perlu mengelola instance printer
  • Ada berbagai pilihan dan tindakan yang akan menghapus kompleksitas pdf.Print (...) (mis. Pembatalan cetak, pemformatan ekstra, dll.)

Saya tidak akan terpaku pada hal itu.

Kevin Hsu
sumber
jawaban yang baik dan praktis; waktu akan memberi tahu bagaimana ini perlu berkembang
Steven A. Lowe
1
Saran singkatnya adalah untuk melihat logika dan data saat menerapkan SRP, untuk memutuskan apakah kami akan menyesal tidak memisahkan mereka lebih awal. Masalah dengan menyimpan pengaturan per-printer di Pdfkelas adalah bahwa mereka tidak seharusnya disimpan bersama - Pdfdisimpan dalam file tetapi pengaturan per-printer harus disimpan dengan profil pengguna / mesin.
rwong