Saya 'menemukan' antarmuka dan saya mulai menyukainya. Keindahan antarmuka adalah bahwa itu adalah kontrak, dan objek apa pun yang memenuhi kontrak itu dapat digunakan di mana pun antarmuka itu diperlukan.
Masalah dengan antarmuka adalah bahwa itu tidak dapat memiliki implementasi standar, yang merupakan rasa sakit untuk properti biasa dan mengalahkan KERING. Ini juga bagus, karena menjaga implementasi dan sistem dipisahkan. Warisan, di tangan, mempertahankan kopling yang lebih ketat, dan memiliki potensi melanggar enkapsulasi.
Kasus 1 (Warisan dengan anggota pribadi, enkapsulasi yang baik, sangat erat)
class Employee
{
int money_earned;
string name;
public:
void do_work(){money_earned++;};
string get_name(return name;);
};
class Nurse : public Employee:
{
public:
void do_work(/*do work. Oops, can't update money_earned. Unaware I have to call superclass' do_work()*/);
};
void HireNurse(Nurse *n)
{
nurse->do_work();
)
Kasus 2 (hanya antarmuka)
class IEmployee
{
virtual void do_work()=0;
virtual string get_name()=0;
};
//class Nurse implements IEmployee.
//But now, for each employee, must repeat the get_name() implementation,
//and add a name member string, which breaks DRY.
Kasus 3: (terbaik dari kedua dunia?)
Mirip dengan Kasus 1 . Namun, bayangkan bahwa (secara hipotetis) C ++ tidak mengizinkan metode utama kecuali metode-metode yang murni virtual .
Jadi, dalam Kasus 1 , mengesampingkan do_work () akan menyebabkan kesalahan waktu kompilasi. Untuk memperbaiki ini, kami menetapkan do_work () sebagai virtual murni, dan menambahkan metode terpisah increment_money_earned (). Sebagai contoh:
class Employee
{
int money_earned;
string name;
public:
virtual void do_work()=0;
void increment_money_earned(money_earned++;);
string get_name(return name;);
};
class Nurse : public Employee:
{
public:
void do_work(/*do work*/ increment_money_earned(); ); .
};
Tetapi bahkan ini memiliki masalah. Bagaimana jika 3 bulan dari sekarang, Joe Coder menciptakan Karyawan Dokter, tetapi ia lupa menelepon increment_money_earned () di do_work ()?
Pertanyaan:
Apakah Case 3 lebih unggul dari Case 1 ? Apakah karena 'enkapsulasi yang lebih baik' atau 'lebih longgar digabungkan', atau karena alasan lain?
Apakah Case 3 lebih unggul dari Case 2 karena sesuai dengan KERING?
sumber
Jawaban:
Salah satu cara untuk menyelesaikan masalah superclass yang lupa menelepon adalah dengan mengembalikan kendali ke superclass! Saya telah jigger contoh pertama Anda untuk menunjukkan bagaimana (dan membuatnya mengkompilasi;)). Oh, saya juga menganggap bahwa
do_work()
dalamEmployee
seharusnyavirtual
dalam contoh pertama Anda.Sekarang
do_work()
tidak dapat ditimpa. Jika Anda ingin memperpanjang, Anda harus melakukannya melaluion_do_work()
yangdo_work()
memiliki kendali atas.Ini, tentu saja, dapat digunakan dengan antarmuka dari contoh kedua Anda juga jika
Employee
diperluas. Jadi, jika saya mengerti Anda dengan benar saya pikir itu membuat Kasus 3 ini tetapi tanpa harus menggunakan C ++ hipotetis! KERING dan memiliki enkapsulasi yang kuat.sumber
Menurut pendapat saya sendiri, antarmuka seharusnya hanya memiliki metode murni - tanpa implementasi standar. Itu tidak melanggar prinsip KERING dengan cara apa pun, karena antarmuka menunjukkan cara mengakses beberapa entitas. Hanya untuk referensi, saya melihat penjelasan KERING di sini :
"Setiap bagian dari pengetahuan harus memiliki representasi otoritatif tunggal, tidak ambigu, dalam suatu sistem."
Di sisi lain, SOLID memberi tahu Anda bahwa setiap kelas harus memiliki antarmuka.
Tidak, kasus 3 tidak lebih unggul daripada kasus 1. Anda harus mengambil keputusan. Jika Anda ingin memiliki implementasi standar maka lakukanlah. Jika Anda ingin metode murni maka ikutilah.
Kemudian Joe Coder harus mendapatkan apa yang pantas diterimanya karena mengabaikan tes unit yang gagal. Dia memang menguji kelas ini, bukan? :)
Satu ukuran tidak cocok untuk semua. Tidak mungkin mengatakan mana yang lebih baik. Ada beberapa kasus di mana yang satu lebih cocok daripada yang lain.
Mungkin Anda harus mempelajari beberapa pola desain daripada mencoba menciptakan pola Anda sendiri.
Saya baru menyadari bahwa Anda mencari pola desain antarmuka non-virtual , karena seperti itulah tampilan case 3 class Anda.
sumber
Antarmuka dapat memiliki implementasi default di C ++. Tidak ada yang mengatakan bahwa implementasi default fungsi tidak hanya tergantung pada anggota virtual lainnya (dan argumen), jadi tidak meningkatkan segala jenis kopling.
Untuk kasus 2, KERING menggantikan di sini. Enkapsulasi ada untuk melindungi program Anda dari perubahan, dari implementasi yang berbeda, tetapi dalam hal ini, Anda tidak memiliki implementasi yang berbeda. Jadi enkapsulasi YAGNI.
Bahkan, antarmuka run-time biasanya dianggap lebih rendah daripada setara dengan waktu kompilasi mereka. Dalam kasus saat kompilasi, Anda dapat memiliki baik kasus 1 dan kasus 2 di bundle- yang sama tidak menyebutkan itu banyak keuntungan lainnya. Atau bahkan pada saat run-time, Anda dapat melakukan
Employee : public IEmployee
keuntungan yang sama secara efektif. Ada banyak cara untuk menangani hal-hal seperti itu.Saya berhenti membaca. YAGNI. C ++ adalah apa itu C ++, dan komite Standar tidak pernah, akan mengimplementasikan perubahan seperti itu, untuk alasan yang sangat baik.
sumber
get_name
. Semua implementasi yang Anda usulkan akan berbagi implementasi yang sama dariget_name
. Selain itu, seperti yang saya katakan, tidak ada alasan untuk memilih, Anda dapat memiliki keduanya. Juga, Kasus 3 sama sekali tidak berharga. Anda dapat mengganti virtual yang tidak murni, jadi lupakan desain yang tidak bisa Anda gunakan.Dari apa yang saya lihat dalam implementasi Anda, implementasi Case 3 Anda membutuhkan kelas abstrak yang dapat mengimplementasikan metode virtual murni yang kemudian dapat diubah di kelas turunan. Kasus 3 akan lebih baik karena kelas turunan dapat mengubah implementasi do_work ketika dan ketika diperlukan dan semua turunan turunan pada dasarnya akan menjadi milik tipe abstrak dasar.
Saya akan mengatakan itu murni tergantung pada desain implementasi Anda dan tujuan yang ingin Anda capai. Kelas abstrak dan Antarmuka diimplementasikan berdasarkan masalah yang harus dipecahkan.
Edit pertanyaan
Tes unit dapat dilakukan untuk memeriksa apakah setiap kelas mengkonfirmasi perilaku yang diharapkan. Jadi, jika unit test yang tepat diterapkan, bug dapat dicegah ketika Joe Coder mengimplementasikan kelas baru.
sumber
Menggunakan antarmuka hanya memecah KERING jika setiap implementasi adalah duplikat dari yang lain. Anda dapat menyelesaikan dilema ini dengan menerapkan antarmuka dan warisan, namun ada beberapa kasus di mana Anda mungkin ingin mengimplementasikan antarmuka yang sama pada sejumlah kelas, tetapi memvariasikan perilaku di setiap kelas, dan ini akan tetap berpegang pada prinsip dari KERING. Apakah Anda memilih untuk menggunakan salah satu dari 3 pendekatan yang telah Anda uraikan, turun ke pilihan yang Anda butuhkan untuk menerapkan teknik terbaik untuk mencocokkan situasi tertentu. Di sisi lain, Anda mungkin akan menemukan bahwa seiring waktu, Anda lebih banyak menggunakan Antarmuka, dan menerapkan pewarisan hanya jika Anda ingin menghapus pengulangan. Itu bukan untuk mengatakan bahwa ini adalah satu - satunya alasan pewarisan, tetapi lebih baik meminimalkan penggunaan warisan untuk memungkinkan Anda menjaga opsi tetap terbuka jika Anda merasa desain Anda perlu diubah nanti, dan jika Anda ingin meminimalkan dampak pada kelas turunan dari efek yang diubah. akan memperkenalkan di kelas induk.
sumber