Saya selalu menyukai gagasan memiliki banyak warisan yang didukung dalam suatu bahasa. Paling sering meskipun itu sengaja dilupakan, dan seharusnya "penggantian" adalah antarmuka. Antarmuka tidak mencakup semua tanah yang sama dengan pewarisan berganda, dan pembatasan ini kadang-kadang dapat menyebabkan lebih banyak kode boilerplate.
Satu-satunya alasan dasar yang pernah saya dengar untuk ini adalah masalah berlian dengan kelas dasar. Aku tidak bisa menerimanya. Bagi saya, itu keluar dari banyak sekali seperti, "Yah, itu mungkin untuk mengacaukannya, jadi itu secara otomatis ide yang buruk." Anda dapat mengacaukan apa pun dalam bahasa pemrograman, dan maksud saya apa pun. Saya tidak bisa menganggap ini serius, setidaknya bukan tanpa penjelasan yang lebih mendalam.
Menyadari masalah ini adalah 90% dari pertempuran. Terlebih lagi, saya pikir saya mendengar sesuatu bertahun-tahun yang lalu tentang penyelesaian untuk keperluan umum yang melibatkan algoritma "amplop" atau sesuatu seperti itu (apakah ini membunyikan bel, siapa pun?).
Mengenai masalah berlian, satu-satunya masalah yang berpotensi asli yang dapat saya pikirkan adalah jika Anda mencoba menggunakan perpustakaan pihak ketiga dan tidak dapat melihat bahwa dua kelas yang tampaknya tidak terkait di perpustakaan itu memiliki kelas dasar yang sama, tetapi selain itu dokumentasi, fitur bahasa yang sederhana dapat, katakanlah, mengharuskan Anda secara khusus menyatakan niat Anda untuk membuat berlian sebelum benar-benar mengkompilasi untuk Anda. Dengan fitur seperti itu, setiap ciptaan berlian adalah disengaja, sembrono, atau karena orang tidak menyadari jebakan ini.
Jadi, semua itu dikatakan ... Apakah ada alasan nyata mengapa kebanyakan orang membenci banyak warisan, atau apakah itu semua hanya histeria yang menyebabkan lebih banyak kerugian daripada kebaikan? Adakah sesuatu yang tidak saya lihat di sini? Terima kasih.
Contoh
Mobil meluas WheeledVehicle, KIASpectra memperluas Mobil dan Elektronik, KIASpectra berisi Radio. Mengapa KIASpectra tidak mengandung Elektronik?
Karena ini elektronik. Pewarisan vs komposisi harus selalu berupa hubungan is-a vs. has-a.
Karena ini elektronik. Ada kabel, papan sirkuit, sakelar, dll semuanya naik turun.
Karena ini elektronik. Jika baterai Anda mati di musim dingin, Anda berada dalam banyak masalah seolah-olah semua roda Anda tiba-tiba hilang.
Mengapa tidak menggunakan antarmuka? Ambil nomor 3, misalnya. Saya tidak ingin menulis ini berulang-ulang, dan saya benar - benar tidak ingin membuat kelas pembantu proxy yang aneh untuk melakukan ini juga:
private void runOrDont()
{
if (this.battery)
{
if (this.battery.working && this.switchedOn)
{
this.run();
return;
}
}
this.dontRun();
}
(Kami tidak membahas apakah implementasi itu baik atau buruk.) Anda dapat membayangkan bagaimana mungkin ada beberapa fungsi yang terkait dengan Elektronik yang tidak terkait dengan apa pun di WheeledVehicle, dan sebaliknya.
Saya tidak yakin apakah akan menerima contoh itu atau tidak, karena ada ruang untuk interpretasi di sana. Anda juga bisa berpikir dalam hal Pesawat memperluas Kendaraan dan FlyingObject dan Burung memperluas Hewan dan FlyingObject, atau dalam hal contoh yang jauh lebih murni.
sumber
Traits
- mereka bertindak seperti antarmuka dengan implementasi opsional, tetapi memiliki beberapa batasan yang membantu mencegah masalah seperti masalah berlian muncul.KiaSpectra
bukan sebuahElectronic
; ia memiliki Elektronika, dan mungkin merupakanElectronicCar
(yang akan memperpanjangCar
...)Jawaban:
Dalam banyak kasus, orang menggunakan warisan untuk memberikan sifat ke suatu kelas. Misalnya pikirkan tentang Pegasus. Dengan pewarisan berganda, Anda mungkin tergoda untuk mengatakan Pegasus memperluas Kuda dan Burung karena Anda telah mengklasifikasikan Burung tersebut sebagai binatang dengan sayap.
Namun, Burung memiliki sifat lain yang tidak dimiliki Pegasi. Misalnya, burung bertelur, Pegasi sudah lahir hidup. Jika pewarisan adalah satu-satunya cara Anda melewati sifat berbagi maka tidak ada cara untuk mengecualikan sifat bertelur dari Pegasus.
Beberapa bahasa telah memilih untuk membuat ciri-ciri konstruk eksplisit dalam bahasa. Lainnya dengan lembut membimbing Anda ke arah itu dengan menghapus MI dari bahasa. Either way, saya tidak bisa memikirkan satu kasus di mana saya pikir "Man saya benar-benar membutuhkan MI untuk melakukan ini dengan benar".
Juga mari kita bahas apa warisan BENAR-BENAR. Ketika Anda mewarisi dari kelas, Anda mengambil ketergantungan pada kelas itu, tetapi juga Anda harus mendukung kontrak yang didukung kelas, baik implisit maupun eksplisit.
Ambil contoh klasik persegi yang diwarisi dari persegi panjang. Persegi panjang memperlihatkan properti panjang dan lebar dan juga metode getPerimeter dan getArea. Kuadrat akan mengesampingkan panjang dan lebar sehingga ketika satu diatur yang lain diatur untuk mencocokkan getPerimeter dan getArea akan bekerja sama (2 * panjang + 2 * lebar untuk perimeter dan panjang * lebar untuk area).
Ada satu test case yang rusak jika Anda mengganti implementasi persegi ini untuk persegi panjang.
Cukup sulit untuk memperbaiki keadaan dengan rantai pewarisan tunggal. Semakin buruk ketika Anda menambahkan yang lain ke dalam campuran.
Jebakan yang saya sebutkan dengan Pegasus di MI dan hubungan Rectangle / Square adalah hasil dari desain yang tidak berpengalaman untuk kelas. Pada dasarnya menghindari banyak warisan adalah cara untuk membantu para pengembang pemula menghindari menembak diri mereka sendiri. Seperti semua prinsip desain lainnya, memiliki disiplin dan pelatihan yang didasarkan pada prinsip-prinsip tersebut memungkinkan Anda untuk segera mengetahui kapan saatnya untuk melepaskan diri darinya. Lihat Dreyfus Model of Skill Acquisition , di tingkat Expert, pengetahuan intrinsik Anda melampaui ketergantungan pada prinsip / prinsip. Anda bisa "merasakan" ketika aturan tidak berlaku.
Dan saya setuju bahwa saya agak tertipu dengan contoh "dunia nyata" tentang mengapa MI disukai.
Mari kita lihat kerangka UI. Secara khusus mari kita lihat beberapa widget yang mungkin pada awalnya terlihat seperti hanya kombinasi dari dua lainnya. Seperti ComboBox. ComboBox adalah TextBox yang memiliki DropDownList yang mendukung. Yaitu saya bisa mengetikkan nilai, atau saya bisa memilih dari daftar nilai yang telah ditentukan. Pendekatan naif adalah mewarisi ComboBox dari TextBox dan DropDownList.
Tapi Textbox Anda mendapatkan nilainya dari apa yang diketik pengguna. Sementara DDL mendapatkan nilainya dari apa yang dipilih pengguna. Siapa yang mengambil preseden? DDL mungkin telah dirancang untuk memverifikasi dan menolak input apa pun yang tidak ada dalam daftar nilai aslinya. Apakah kita mengabaikan logika itu? Itu berarti kita harus mengekspos logika internal untuk ditimpa oleh pewaris. Atau lebih buruk, tambahkan logika ke kelas dasar yang hanya ada untuk mendukung subclass (melanggar Prinsip Ketergantungan Inversi ).
Menghindari MI membantu Anda menghindari perangkap ini sama sekali. Dan mungkin menyebabkan Anda mengekstraksi ciri-ciri umum dari widget UI Anda sehingga dapat diterapkan sesuai kebutuhan. Contoh yang sangat baik dari ini adalah Properti Terlampir WPF yang memungkinkan elemen kerangka kerja di WPF untuk menyediakan properti yang elemen kerangka kerja lain dapat digunakan tanpa mewarisi dari elemen kerangka induk.
Misalnya Grid adalah panel tata letak di WPF dan memiliki properti Kolom dan Baris yang menentukan di mana elemen anak harus ditempatkan dalam pengaturan grid. Tanpa properti terlampir, jika saya ingin mengatur Tombol di dalam Kotak, Tombol harus berasal dari Kotak sehingga bisa memiliki akses ke properti Kolom dan Baris.
Pengembang mengambil konsep ini lebih jauh dan menggunakan properti yang dilampirkan sebagai cara perilaku komponen (misalnya di sini adalah posting saya tentang membuat GridView yang dapat disortir menggunakan properti terlampir yang ditulis sebelum WPF menyertakan DataGrid). Pendekatan ini telah diakui sebagai Pola Desain XAML yang disebut Perilaku Terlampir .
Semoga ini memberikan sedikit lebih banyak wawasan tentang mengapa Multiple Inheritance biasanya disukai.
sumber
Mengizinkan banyak pewarisan membuat aturan tentang kelebihan fungsi dan pengiriman virtual jelas lebih rumit, serta implementasi bahasa di sekitar tata letak objek. Desainer / pelaksana bahasa berdampak ini sedikit, dan meningkatkan standar yang sudah tinggi untuk menyelesaikan bahasa, stabil dan diadopsi.
Argumen umum lainnya yang pernah saya lihat (dan buat beberapa kali) adalah bahwa dengan memiliki dua kelas dasar +, objek Anda hampir selalu melanggar Prinsip Tanggung Jawab Tunggal. Baik dua kelas dasar + adalah kelas mandiri yang bagus dengan tanggung jawab mereka sendiri (menyebabkan pelanggaran) atau mereka adalah tipe parsial / abstrak yang saling bekerja sama untuk membuat satu tanggung jawab kohesif tunggal.
Dalam kasus lain ini, Anda memiliki 3 skenario:
Secara pribadi, saya pikir multiple inheritance memiliki rap yang buruk, dan bahwa sistem komposisi gaya sifat yang dilakukan dengan baik akan sangat kuat / berguna ... tetapi ada banyak cara yang dapat diterapkan dengan buruk, dan banyak alasannya bukan ide yang baik dalam bahasa seperti C ++.
Mengenai contoh Anda, itu tidak masuk akal. A Kia memiliki elektronik. Ia memiliki mesin. Demikian juga, elektronik itu memiliki sumber daya, yang kebetulan merupakan aki mobil. Warisan, apalagi pewarisan berganda tidak memiliki tempat di sana.
sumber
Satu-satunya alasan mengapa itu dilarang adalah karena itu memudahkan orang untuk menembak diri mereka sendiri di kaki.
Apa yang biasanya terjadi dalam diskusi semacam ini adalah argumen tentang apakah fleksibilitas memiliki alat lebih penting daripada keamanan tidak menembak kaki Anda. Tidak ada jawaban yang jelas benar untuk argumen itu, karena seperti kebanyakan hal lain dalam pemrograman, jawabannya tergantung pada konteks.
Jika pengembang Anda nyaman dengan MI, dan MI masuk akal dalam konteks apa yang Anda lakukan, maka Anda akan sangat merindukannya dalam bahasa yang tidak mendukungnya. Pada saat yang sama jika tim tidak nyaman dengan itu, atau tidak ada kebutuhan nyata untuk itu dan orang menggunakannya 'hanya karena mereka bisa', maka itu kontraproduktif.
Tapi tidak, tidak ada argumen yang benar-benar meyakinkan yang membuktikan bahwa pewarisan berganda adalah ide yang buruk.
SUNTING
Jawaban atas pertanyaan ini tampaknya bulat. Demi menjadi pendukung iblis, saya akan memberikan contoh bagus tentang pewarisan berganda, di mana tidak melakukannya akan menyebabkan peretasan.
Misalkan Anda sedang merancang aplikasi pasar modal. Anda memerlukan model data untuk sekuritas. Beberapa sekuritas adalah produk ekuitas (saham, trust investasi real estat, dll) yang lain adalah utang (obligasi, obligasi korporasi), yang lain adalah derivatif (opsi, berjangka). Jadi jika Anda menghindari MI, Anda akan membuat pohon warisan yang sangat jelas dan sederhana. Saham akan mewarisi Ekuitas, Obligasi akan mewarisi Hutang. Sejauh ini bagus, tapi bagaimana dengan turunannya? Mereka dapat didasarkan dari produk seperti Ekuitas atau produk seperti Debit? Ok, saya kira kita akan membuat cabang pohon warisan kita lebih banyak. Perlu diingat, beberapa derivatif didasarkan pada produk ekuitas, produk utang, atau tidak keduanya. Jadi pohon warisan kita semakin rumit. Kemudian datanglah analis bisnis dan memberi tahu Anda bahwa sekarang kami mendukung sekuritas yang diindeks (opsi indeks, opsi indeks masa depan). Dan hal-hal ini dapat didasarkan pada Ekuitas, Hutang, atau Derivatif. Ini semakin berantakan! Apakah opsi indeks masa depan saya menghasilkan Ekuitas-> Saham-> Opsi-> Indeks? Mengapa tidak Equity-> Stock-> Index-> Option? Bagaimana jika suatu hari saya menemukan keduanya dalam kode saya (Ini terjadi; kisah nyata)?
Masalahnya di sini adalah bahwa tipe-tipe fundamental ini dapat dicampur dalam permutasi yang tidak secara alami mengambil satu dari yang lain. Benda-benda didefinisikan oleh adalah hubungan, sehingga komposisi sangat tidak masuk akal. Warisan berganda (atau konsep mixin yang serupa) adalah satu-satunya representasi logis di sini.
Solusi nyata untuk masalah ini adalah memiliki jenis Indeks, Hutang, Derivatif, Indeks yang ditentukan dan dicampur menggunakan beberapa pewarisan untuk membuat model data Anda. Ini akan membuat objek yang keduanya, masuk akal, dan mudah dipinjamkan untuk digunakan kembali.
sumber
Equity
danDebt
keduanya mengimplementasikanISecurity
.Derivative
memilikiISecurity
properti. Mungkin itu sendiriISecurity
jika pantas (saya tidak tahu keuangan).IndexedSecurities
sekali lagi berisi properti antarmuka yang diterapkan pada jenis yang diizinkan didasarkan. Jika semuanyaISecurity
, maka mereka semua memilikiISecurity
properti dan dapat disarangkan secara sewenang-wenang ...Jawaban lain di sini tampaknya sebagian besar masuk ke teori. Jadi, inilah contoh Python konkret, disederhanakan ke bawah, yang telah saya hancurkan dengan cepat, yang membutuhkan cukup banyak refactoring:
Bar
ditulis dengan asumsi itu memiliki implementasi sendirizeta()
, yang umumnya merupakan asumsi yang cukup bagus. Subclass harus menimpanya dengan tepat sehingga melakukan hal yang benar. Sayangnya, nama-nama itu hanya kebetulan sama - mereka melakukan hal-hal yang agak berbeda, tetapiBar
sekarang memanggilFoo
implementasi:Agak membuat frustasi ketika tidak ada kesalahan yang dilemparkan, aplikasi mulai bertindak sedikit salah, dan perubahan kode yang menyebabkannya (membuat
Bar.zeta
) sepertinya tidak menjadi masalah.sumber
super()
?super()
beredarBang
keBar, Foo
juga akan memperbaiki masalah - tetapi poin Anda bagus, +1.Bar.zeta(self)
.Saya berpendapat bahwa tidak ada masalah nyata dengan MI dalam bahasa yang benar . Kuncinya adalah untuk memungkinkan struktur berlian, tetapi mengharuskan subtipe menyediakan override sendiri, alih-alih kompiler memilih salah satu implementasi berdasarkan beberapa aturan.
Saya melakukan ini di Jambu , bahasa yang sedang saya kerjakan. Salah satu fitur dari Guava adalah bahwa kita dapat memanggil implementasi metode supertype spesifik. Jadi mudah untuk menunjukkan implementasi supertipe mana yang harus "diwariskan", tanpa sintaks khusus:
Jika kami tidak memberikannya
OrderedSet
sendiritoString
, kami akan mendapatkan kesalahan kompilasi. Tidak ada kejutan.Saya menemukan MI sangat berguna dengan koleksi. Sebagai contoh, saya suka menggunakan
RandomlyEnumerableSequence
tipe untuk menghindari menyatakangetEnumerator
untuk array, deques, dan sebagainya:Jika kita tidak memiliki MI, kita dapat menulis
RandomAccessEnumerator
untuk beberapa koleksi untuk digunakan, tetapi harus menulisgetEnumerator
metode singkat masih menambahkan boilerplate.Demikian pula, MI berguna untuk mewarisi implementasi standar
equals
,hashCode
dantoString
untuk koleksi.sumber
toString
perilaku, jadi mengesampingkan perilakunya tidak melanggar prinsip substitusi. Anda juga terkadang mendapatkan "konflik" antara metode yang memiliki perilaku yang sama tetapi algoritma yang berbeda, terutama dengan koleksi.Ordered extends Set and Sequence
aDiamond
? Itu hanya sebuah Gabung. Tidak memilikihat
titik. Mengapa Anda menyebutnya berlian? Saya bertanya di sini tetapi sepertinya pertanyaan tabu. Bagaimana Anda tahu bahwa Anda perlu menyebut struktur triangualar ini sebagai Berlian daripada Gabung?Top
tipe implisit yang menyatakantoString
, jadi sebenarnya ada struktur berlian. Tapi saya pikir Anda ada benarnya - "bergabung" tanpa struktur berlian menciptakan masalah yang sama, dan sebagian besar bahasa menangani kedua kasus dengan cara yang sama.Warisan, banyak atau tidak, tidak begitu penting. Jika dua objek dari jenis yang berbeda dapat diganti, itulah yang penting, bahkan jika mereka tidak dihubungkan oleh warisan.
Daftar yang ditautkan dan string karakter memiliki sedikit kesamaan, dan tidak perlu ditautkan oleh pewarisan, tetapi ini berguna jika saya dapat menggunakan
length
fungsi untuk mendapatkan jumlah elemen dalam salah satu dari keduanya.Warisan adalah trik untuk menghindari implementasi berulang kode. Jika pewarisan menghemat pekerjaan Anda, dan banyak pewarisan menghemat lebih banyak pekerjaan dibandingkan dengan pewarisan tunggal, maka itu semua pembenaran yang diperlukan.
Saya menduga bahwa beberapa bahasa tidak menerapkan banyak pewarisan dengan sangat baik, dan bagi para praktisi dari bahasa-bahasa tersebut, itulah yang dimaksud dengan pewarisan berganda. Sebutkan multiple inheritance ke seorang programmer C ++, dan apa yang terlintas dalam pikiran adalah sesuatu tentang masalah ketika sebuah kelas berakhir dengan dua salinan basis melalui dua jalur pewarisan yang berbeda, dan apakah akan digunakan
virtual
pada kelas dasar, dan kebingungan tentang bagaimana destructor disebut , dan seterusnya.Dalam banyak bahasa, pewarisan kelas digabung dengan pewarisan simbol. Ketika Anda menurunkan kelas D dari kelas B, Anda tidak hanya membuat hubungan tipe, tetapi karena kelas-kelas ini juga berfungsi sebagai ruang nama leksikal, Anda berhadapan dengan impor simbol dari ruang nama B ke ruang nama D, di samping semantik dari apa yang terjadi dengan tipe B dan D sendiri. Karenanya, pewarisan berganda membawa masalah bentrok simbol. Jika kita mewarisi dari
card_deck
dangraphic
, keduanya "memiliki"draw
metode, apa artinya dengandraw
objek yang dihasilkan? Sistem objek yang tidak memiliki masalah ini adalah yang ada di Common Lisp. Mungkin tidak secara kebetulan, multiple inheritance digunakan dalam program Lisp.Implementasi yang buruk, segala sesuatu yang tidak nyaman (seperti multiple inheritance) harus dibenci.
sumber
length
operasi berguna secara independen dari konsep pewarisan (yang dalam hal ini tidak membantu: kita tidak mungkin dapat mencapai apa pun dengan mencoba berbagi implementasi antara dua metodelength
fungsi). Namun demikian mungkin ada beberapa pewarisan abstrak: misalnya daftar dan string berurutan jenis (tetapi yang tidak menyediakan implementasi apa pun).Sejauh yang saya tahu, bagian dari masalah (selain membuat desain Anda sedikit lebih sulit untuk dipahami (namun lebih mudah untuk kode)) adalah bahwa kompiler akan menghemat ruang yang cukup untuk data kelas Anda, memungkinkan ini sejumlah besar pemborosan memori dalam kasus berikut:
(Contoh saya mungkin bukan yang terbaik, tetapi cobalah untuk mendapatkan inti tentang beberapa ruang memori untuk tujuan yang sama, itu adalah hal pertama yang terlintas di pikiran saya: P)
Mempertimbangkan DDD di mana anjing kelas meluas dari caninus dan pet, caninus memiliki variabel yang menunjukkan jumlah makanan yang harus dimakan (bilangan bulat) dengan nama dietKg, tetapi hewan peliharaan juga memiliki variabel lain untuk tujuan itu biasanya di bawah yang sama nama (kecuali jika Anda menetapkan nama variabel lain, maka Anda akan memiliki kode tambahan kode, yang merupakan masalah awal yang ingin Anda hindari, untuk menangani dan menjaga integritas variabel bouth), maka Anda akan memiliki dua ruang memori untuk tepat tujuan yang sama, untuk menghindari ini, Anda harus memodifikasi kompiler Anda untuk mengenali nama itu di bawah namespace yang sama dan hanya menetapkan ruang memori tunggal untuk data itu, yang sayangnya tidak dapat ditentukan dalam waktu kompilasi.
Anda bisa, tentu saja, merancang bahasa untuk menentukan bahwa variabel tersebut mungkin sudah memiliki ruang yang ditentukan di tempat lain, tetapi pada akhirnya programmer harus menentukan di mana ruang memori yang variabel ini rujuk (dan lagi kode tambahan).
Percayalah pada saya, orang-orang yang menerapkan pemikiran ini sangat keras tentang semua ini, tetapi saya senang Anda bertanya, prespektif Anda yang baik adalah yang mengubah paradigma;), dan pertimbangkan ini, saya tidak mengatakan itu tidak mungkin (tetapi banyak asumsi dan kompiler multi-fase harus diimplementasikan, dan yang benar-benar kompleks), saya hanya mengatakan itu belum ada, jika Anda memulai proyek untuk kompiler Anda sendiri yang mampu melakukan "ini" (multiple inheritance) tolong beri tahu saya, saya Dengan senang hati akan bergabung dengan tim Anda.
sumber
Untuk beberapa saat sekarang tidak pernah benar-benar terjadi pada saya betapa sangat berbeda beberapa tugas pemrograman dari yang lain - dan seberapa besar membantu jika bahasa dan pola yang digunakan disesuaikan dengan ruang masalah.
Ketika Anda bekerja sendirian atau sebagian besar terisolasi pada kode yang Anda tulis itu adalah ruang masalah yang sama sekali berbeda dengan mewarisi basis kode dari 40 orang di India yang mengerjakannya selama setahun sebelum menyerahkannya kepada Anda tanpa bantuan transisi.
Bayangkan Anda baru saja direkrut oleh perusahaan impian Anda dan kemudian mewarisi basis kode seperti itu. Lebih jauh bayangkan bahwa konsultan telah belajar tentang (dan karena itu tergila-gila dengan) warisan dan multiple-inheritance ... Bisakah Anda menggambarkan apa yang sedang Anda kerjakan.
Ketika Anda mewarisi kode, fitur yang paling penting adalah bahwa kode itu dapat dimengerti dan bagian-bagiannya terisolasi sehingga dapat dikerjakan secara mandiri. Tentu ketika Anda pertama kali menulis struktur kode seperti multiple inheritance dapat menyelamatkan Anda dari duplikasi kecil dan tampaknya sesuai dengan suasana hati logis Anda pada saat itu, tetapi orang berikutnya hanya memiliki lebih banyak hal untuk diurai.
Setiap interkoneksi dalam kode Anda juga membuatnya lebih sulit untuk memahami dan memodifikasi karya secara independen, dua kali lipat dengan pewarisan berganda.
Ketika Anda bekerja sebagai bagian dari tim Anda ingin menargetkan kode yang paling sederhana yang memberi Anda sama sekali tidak ada logika yang berlebihan (Itulah arti sebenarnya KERING, bukan berarti Anda tidak perlu mengetik banyak hanya karena Anda tidak perlu mengubah kode dalam 2 tempat untuk memecahkan masalah!)
Ada cara yang lebih sederhana untuk mencapai kode KERING daripada Multiple Inheritance, jadi memasukkannya dalam bahasa hanya dapat membuka Anda terhadap masalah yang dimasukkan oleh orang lain yang mungkin tidak memiliki tingkat pemahaman yang sama seperti Anda. Ini bahkan menggoda jika bahasa Anda tidak mampu menawarkan Anda cara yang sederhana / tidak rumit untuk menjaga kode Anda KERING.
sumber
Argumen terbesar terhadap pewarisan berganda adalah bahwa beberapa kemampuan yang berguna dapat disediakan, dan beberapa aksioma bermanfaat dapat bertahan, dalam kerangka kerja yang sangat membatasi itu (*), tetapi tidak dapat tidak dapat disediakan dan / atau ditahan tanpa batasan seperti itu. Diantara mereka:
Kemampuan untuk memiliki beberapa modul yang dikompilasi secara terpisah termasuk kelas-kelas yang mewarisi dari kelas-kelas modul lain, dan mengkompilasi ulang modul yang berisi kelas dasar tanpa harus mengkompilasi ulang setiap modul yang mewarisi dari kelas dasar itu.
Kemampuan untuk tipe mewarisi implementasi anggota dari kelas induk tanpa tipe turunan harus mengimplementasikannya kembali
Aksioma bahwa setiap instance objek dapat di-upcast atau downcast langsung ke dirinya sendiri atau salah satu dari jenis dasarnya, dan upcast dan downcast tersebut, dalam kombinasi atau urutan, selalu mempertahankan identitas
Aksioma bahwa jika kelas turunan menimpa dan rantai ke anggota kelas dasar, anggota basis akan dipanggil langsung oleh kode rantai, dan tidak akan dipanggil di tempat lain.
(*) Biasanya dengan mengharuskan jenis yang mendukung multiple inheritance dideklarasikan sebagai "antarmuka" daripada kelas, dan tidak memungkinkan antarmuka untuk melakukan semua yang dapat dilakukan oleh kelas normal.
Jika seseorang ingin mengizinkan pewarisan berganda yang digeneralisasi, sesuatu yang lain harus memberi jalan. Jika X dan Y keduanya mewarisi dari B, mereka berdua menimpa anggota M yang sama dan rantai ke implementasi basis, dan jika D mewarisi dari X dan Y tetapi tidak menimpa M, maka diberi contoh q dari tipe D, apa yang harus (( B) q) .M () lakukan? Menolak gips semacam itu akan melanggar aksioma yang mengatakan objek apa pun dapat di-upcast ke tipe pangkalan apa pun, tetapi perilaku yang mungkin dilakukan para pemain dan pemanggilan anggota akan melanggar aksioma mengenai metode chaining. Orang bisa meminta bahwa kelas hanya dimuat dalam kombinasi dengan versi tertentu dari kelas dasar yang mereka susun, tetapi itu sering kali canggung. Memiliki runtime yang menolak untuk memuat jenis apa pun di mana leluhur mana pun dapat dijangkau oleh lebih dari satu rute mungkin bisa diterapkan, tetapi akan sangat membatasi kegunaan pewarisan berganda. Mengizinkan jalur pewarisan bersama hanya ketika tidak ada konflik yang akan menciptakan situasi di mana versi lama X akan kompatibel dengan Y lama atau baru, dan Y lama akan kompatibel dengan X lama atau baru, tetapi X baru dan Y baru akan kompatibel, bahkan jika keduanya tidak melakukan sesuatu yang dengan sendirinya harus diharapkan sebagai perubahan yang melanggar.
Beberapa bahasa dan kerangka kerja memungkinkan pewarisan berganda, berdasarkan teori bahwa apa yang diperoleh dari MI lebih penting daripada apa yang harus diberikan untuk mengizinkannya. Biaya MI signifikan, namun, dan untuk banyak kasus antarmuka memberikan 90% manfaat MI dengan sebagian kecil dari biaya.
sumber
FirstDerivedFoo
penimpaanBar
tidak melakukan apa pun selain rantai ke non-virtual yang dilindungiFirstDerivedFoo_Bar
, danSecondDerivedFoo
menimpa rantai ke non-virtual yang dilindungiSecondDerivedBar
, dan jika kode yang ingin mengakses metode dasar menggunakan metode non-virtual yang dilindungi itu, maka mungkin pendekatan yang bisa diterapkan ...base.VirtualMethod
) tetapi saya tidak tahu adanya dukungan bahasa untuk secara khusus memfasilitasi pendekatan semacam itu. Saya berharap ada cara bersih untuk melampirkan satu blok kode ke metode yang dilindungi non-virtual dan metode virtual dengan tanda tangan yang sama tanpa memerlukan metode virtual "satu-liner" (yang akhirnya mengambil lebih banyak dari satu baris) dalam kode sumber).