Mengapa saya harus menghindari beberapa pewarisan dalam C ++?


167

Apakah ini konsep yang baik untuk menggunakan banyak warisan atau dapatkah saya melakukan hal-hal lain?

Jawaban:


259

Warisan berganda (disingkat MI) berbau , yang berarti bahwa biasanya , itu dilakukan karena alasan yang buruk, dan itu akan meledak kembali di hadapan sang pengelola.

Ringkasan

  1. Pertimbangkan komposisi fitur, alih-alih warisan
  2. Waspadai Diamond of Dread
  3. Pertimbangkan pewarisan banyak antarmuka, bukan objek
  4. Terkadang, Multiple Inheritance adalah hal yang benar. Jika ya, maka gunakan.
  5. Bersiaplah untuk mempertahankan arsitektur multi-warisan Anda dalam ulasan kode

1. Mungkin komposisi?

Ini berlaku untuk warisan, dan bahkan lebih benar untuk warisan ganda.

Apakah objek Anda benar-benar perlu diwarisi dari yang lain? A Cartidak perlu mewarisi dari Enginebekerja, atau dari Wheel. A Carmemiliki satu Enginedan empat Wheel.

Jika Anda menggunakan banyak pewarisan untuk menyelesaikan masalah ini alih-alih komposisi, maka Anda telah melakukan kesalahan.

2. Berlian Ketakutan

Biasanya, Anda memiliki kelas A, lalu Bdan Ckeduanya mewarisi dari A. Dan (jangan tanya kenapa) seseorang kemudian memutuskan yang Dharus mewarisi dari Bdan C.

Saya telah mengalami masalah seperti ini dua kali dalam 8 malam, dan itu menyenangkan untuk dilihat karena:

  1. Berapa banyak kesalahan itu dari awal (Dalam kedua kasus, Dseharusnya tidak diwarisi dari keduanya Bdan C), karena ini adalah arsitektur yang buruk (pada kenyataannya, Cseharusnya tidak ada sama sekali ...)
  2. Berapa banyak pengelola membayar untuk itu, karena di C ++, kelas induk Ahadir dua kali di kelas cucu-nya D, dan dengan demikian, memperbarui satu bidang induk A::fieldberarti memperbaharuinya dua kali (melalui B::fielddan C::field), atau membuat sesuatu berjalan salah dan diam, kemudian (barukan pointer B::field, dan hapus C::field...)

Menggunakan kata kunci virtual dalam C ++ untuk memenuhi syarat warisan menghindari tata letak ganda yang dijelaskan di atas jika ini bukan yang Anda inginkan, tetapi bagaimanapun, dalam pengalaman saya, Anda mungkin melakukan sesuatu yang salah ...

Dalam hierarki Objek, Anda harus mencoba menjaga hierarki sebagai Tree (node ​​memiliki SATU induk), bukan sebagai grafik.

Lebih lanjut tentang Diamond (sunting 2017-05-03)

Masalah sebenarnya dengan Diamond of Dread di C ++ ( dengan asumsi desainnya bagus - periksa kode Anda! ), Adalah Anda harus membuat pilihan :

  • Apakah diinginkan agar kelas Aada dua kali dalam tata letak Anda, dan apa artinya? Jika ya, berarti mewarisi dari itu dua kali.
  • jika itu harus ada hanya sekali, maka mewarisinya secara virtual.

Pilihan ini melekat pada masalah, dan dalam C ++, tidak seperti bahasa lain, Anda sebenarnya dapat melakukannya tanpa dogma yang memaksa desain Anda di tingkat bahasa.

Tetapi seperti semua kekuatan, dengan kekuatan itu datang tanggung jawab: Minta desain Anda ditinjau.

3. Antarmuka

Berbagai warisan nol atau satu kelas konkret, dan nol atau lebih antarmuka biasanya Oke, karena Anda tidak akan menemukan Diamond of Dread yang dijelaskan di atas. Sebenarnya, inilah yang dilakukan di Jawa.

Biasanya, apa yang Anda maksudkan ketika C mewarisi dari Adan Badalah bahwa pengguna dapat menggunakan Cseolah-olah itu adalah A, dan / atau seolah-olah itu adalah B.

Dalam C ++, sebuah antarmuka adalah kelas abstrak yang memiliki:

  1. semua metodenya dinyatakan virtual murni (diakhiri dengan = 0) (menghapus 2017-05-03)
  2. tidak ada variabel anggota

Warisan berganda dari nol ke satu objek nyata, dan nol atau lebih antarmuka tidak dianggap "bau" (setidaknya, tidak sebanyak).

Lebih lanjut tentang C ++ Abstract Interface (sunting 2017-05-03)

Pertama, pola NVI dapat digunakan untuk menghasilkan antarmuka, karena kriteria sebenarnya adalah tidak memiliki status (yaitu tidak ada variabel anggota, kecuali this). Titik antarmuka abstrak Anda adalah untuk menerbitkan kontrak ("Anda dapat memanggil saya dengan cara ini, dan dengan cara ini"), tidak lebih, tidak kurang. Keterbatasan hanya memiliki metode virtual abstrak harus menjadi pilihan desain, bukan kewajiban.

Kedua, dalam C ++, masuk akal untuk mewarisi secara virtual dari antarmuka abstrak, (bahkan dengan biaya tambahan / tipuan). Jika tidak, dan pewarisan antarmuka muncul beberapa kali dalam hierarki Anda, maka Anda akan memiliki ambiguitas.

Ketiga, orientasi objek itu bagus, tetapi bukan The Only Truth Out There TM di C ++. Gunakan alat yang tepat, dan selalu ingat Anda memiliki paradigma lain di C ++ yang menawarkan berbagai jenis solusi.

4. Apakah Anda benar-benar membutuhkan Multiple Inheritance?

Terkadang ya.

Biasanya, Ckelas Anda mewarisi dari Adan B, dan Adan Badalah dua objek yang tidak terkait (yaitu tidak dalam hierarki yang sama, tidak ada kesamaan, konsep yang berbeda, dll.).

Misalnya, Anda dapat memiliki sistem Nodesdengan koordinat X, Y, Z, dapat melakukan banyak perhitungan geometris (mungkin sebuah titik, bagian dari objek geometris) dan setiap Node adalah Agen Otomatis, yang dapat berkomunikasi dengan agen lain.

Mungkin Anda sudah memiliki akses ke dua perpustakaan, masing-masing dengan ruang nama sendiri (alasan lain untuk menggunakan ruang nama ... Tapi Anda menggunakan ruang nama, bukan?), Satu makhluk geodan makhluk lainnyaai

Jadi, Anda memiliki own::Nodeturunan sendiri dari ai::Agentdan geo::Point.

Inilah saatnya ketika Anda harus bertanya pada diri sendiri apakah sebaiknya Anda tidak menggunakan komposisi. Jika own::Nodebenar-benar baik a ai::Agentdan a geo::Point, maka komposisi tidak akan berfungsi.

Maka Anda akan membutuhkan banyak pewarisan, setelah Anda own::Nodeberkomunikasi dengan agen lain sesuai dengan posisi mereka dalam ruang 3D.

(Anda akan mencatat bahwa ai::Agentdan geo::Pointbenar-benar, sepenuhnya, sepenuhnya tidak terkait ... Ini secara drastis mengurangi bahaya multiple inheritance)

Kasus lain (edit 2017-05-03)

Ada beberapa kasus lain:

  • menggunakan (semoga pribadi) warisan sebagai detail implementasi
  • beberapa idiom C ++ seperti kebijakan dapat menggunakan banyak pewarisan (ketika masing-masing bagian perlu berkomunikasi dengan yang lain melalui this)
  • warisan virtual dari std :: exception ( Apakah Warisan Virtual diperlukan untuk Pengecualian? )
  • dll.

Terkadang Anda dapat menggunakan komposisi, dan terkadang MI lebih baik. Intinya adalah: Anda punya pilihan. Lakukan dengan bertanggung jawab (dan minta kode Anda ditinjau).

5. Jadi, haruskah saya melakukan Multiple Inheritance?

Sebagian besar waktu, dalam pengalaman saya, tidak. MI bukanlah alat yang tepat, meskipun tampaknya berfungsi, karena dapat digunakan oleh malas untuk menumpuk fitur bersama tanpa menyadari konsekuensinya (seperti membuat Carkeduanya Enginedan a Wheel).

Tapi terkadang, ya. Dan pada saat itu, tidak ada yang lebih baik daripada MI.

Tetapi karena MI bau, bersiaplah untuk mempertahankan arsitektur Anda dalam ulasan kode (dan mempertahankannya adalah hal yang baik, karena jika Anda tidak dapat mempertahankannya, maka Anda seharusnya tidak melakukannya).


4
Saya berpendapat bahwa itu tidak pernah diperlukan dan sangat jarang berguna bahwa bahkan jika itu sangat cocok dengan penghematan daripada delegasi tidak akan mengkompensasi kebingungan untuk programmer tidak terbiasa menggunakannya, tetapi ringkasan yang baik.
Bill K

2
Saya akan menambahkan poin 5, bahwa kode yang menggunakan MI harus memiliki komentar tepat di sebelahnya menjelaskan alasannya, jadi Anda tidak perlu menjelaskannya secara lisan dalam tinjauan kode. Tanpa ini maka seseorang yang bukan pengulas Anda mungkin mempertanyakan penilaian baik Anda ketika mereka melihat kode Anda dan Anda mungkin tidak memiliki kesempatan untuk mempertahankannya.
Tim Abell

" Karena Anda tidak akan menemukan Berlian Ketakutan yang dijelaskan di atas. " Mengapa tidak?
curiousguy

1
Saya menggunakan MI untuk pola pengamat.
Calmarius

13
@ BillK: Tentu saja tidak perlu. Jika Anda memiliki bahasa lengkap Turning tanpa MI, Anda dapat melakukan apa saja yang dapat dilakukan oleh bahasa dengan MI. Jadi ya, itu tidak perlu. Pernah. Yang mengatakan, itu bisa menjadi alat yang sangat berguna, dan apa yang disebut Dreaded Diamond ... Saya tidak pernah benar-benar mengerti mengapa kata "dreaded" bahkan ada di sana. Sebenarnya cukup mudah untuk bernalar dan biasanya tidak masalah.
Thomas Eding

145

Dari wawancara dengan Bjarne Stroustrup :

Orang-orang mengatakan dengan benar bahwa Anda tidak memerlukan banyak warisan, karena apa pun yang dapat Anda lakukan dengan banyak warisan juga dapat Anda lakukan dengan satu warisan. Anda cukup menggunakan trik delegasi yang saya sebutkan. Selain itu, Anda tidak memerlukan warisan sama sekali, karena apa pun yang Anda lakukan dengan warisan tunggal Anda juga dapat melakukannya tanpa warisan dengan meneruskan melalui kelas. Sebenarnya, Anda tidak memerlukan kelas apa pun, karena Anda dapat melakukan semuanya dengan pointer dan struktur data. Tetapi mengapa Anda ingin melakukan itu? Kapan nyaman menggunakan fasilitas bahasa? Kapan Anda lebih suka solusi? Saya telah melihat kasus-kasus di mana pewarisan berganda berguna, dan saya bahkan telah melihat kasus-kasus di mana pewarisan berganda yang cukup rumit bermanfaat. Secara umum, saya lebih suka menggunakan fasilitas yang ditawarkan oleh bahasa untuk melakukan penyelesaian masalah


6
Ada beberapa fitur C ++ yang saya lewatkan di C # dan Java, meskipun memiliki mereka akan membuat desain lebih sulit / rumit. Sebagai contoh, jaminan kekekalan const- Saya harus menulis solusi yang kikuk (biasanya menggunakan antarmuka dan komposisi) ketika sebuah kelas benar - benar perlu memiliki varian yang bisa berubah dan tidak dapat diubah. Namun, saya tidak pernah sekali pun melewatkan banyak warisan, dan tidak pernah merasa bahwa saya harus menulis solusi karena kurangnya fitur ini. Itulah bedanya. Dalam setiap kasus yang pernah saya lihat, tidak menggunakan MI adalah pilihan desain yang lebih baik, bukan solusi.
BlueRaja - Danny Pflughoeft

24
+1: Jawaban sempurna: Jika Anda tidak ingin menggunakannya, maka jangan menggunakannya.
Thomas Eding

38

Tidak ada alasan untuk menghindarinya dan itu bisa sangat berguna dalam situasi. Anda harus menyadari masalah potensial.

Yang terbesar adalah intan kematian:

class GrandParent;
class Parent1 : public GrandParent;
class Parent2 : public GrandParent;
class Child : public Parent1, public Parent2;

Anda sekarang memiliki dua "salinan" GrandParent di dalam Child.

C ++ telah memikirkan hal ini dan memungkinkan Anda melakukan pewarisan virtual untuk menyelesaikan masalah.

class GrandParent;
class Parent1 : public virtual GrandParent;
class Parent2 : public virtual GrandParent;
class Child : public Parent1, public Parent2;

Selalu tinjau desain Anda, pastikan Anda tidak menggunakan warisan untuk menghemat penggunaan kembali data. Jika Anda dapat mewakili hal yang sama dengan komposisi (dan biasanya Anda bisa) ini adalah pendekatan yang jauh lebih baik.


21
Dan jika Anda melarang warisan dan hanya komposisi penggunaan gantinya, Anda selalu memiliki dua GrandParentdi Child. Ada rasa takut terhadap MI, karena orang hanya berpikir mereka mungkin tidak mengerti aturan bahasa. Tetapi siapa pun yang tidak bisa mendapatkan aturan sederhana ini juga tidak bisa menulis program yang tidak sepele.
curiousguy

1
Kedengarannya kasar, aturan C ++ serba sangat rumit. OK, jadi meskipun aturan individu itu sederhana, ada banyak dari mereka, yang membuat semuanya tidak sederhana, setidaknya untuk diikuti manusia.
Zebrafish

11

Lihat w: Warisan Berganda .

Warisan berganda telah menerima kritik dan karenanya tidak diterapkan dalam banyak bahasa. Kritik meliputi:

  • Meningkatnya kompleksitas
  • Ambiguitas semantik sering diringkas sebagai masalah intan .
  • Tidak dapat mewarisi beberapa kali secara eksplisit dari satu kelas
  • Urutan warisan mengubah semantik kelas.

Berbagai pewarisan dalam bahasa dengan konstruktor gaya C ++ / Java memperparah masalah pewarisan konstruktor dan rantai pengarah, sehingga menciptakan masalah pemeliharaan dan ekstensibilitas dalam bahasa ini. Objek dalam hubungan warisan dengan metode konstruksi yang sangat bervariasi sulit untuk diterapkan di bawah paradigma chaining konstruktor.

Cara modern untuk menyelesaikan ini menggunakan antarmuka (kelas abstrak murni) seperti COM dan antarmuka Java.

Saya dapat melakukan hal-hal lain menggantikan ini?

Ya kamu bisa. Saya akan mencuri dari GoF .

  • Program ke Antarmuka, bukan Implementasi
  • Lebih suka komposisi daripada warisan

8

Warisan publik adalah hubungan IS-A, dan kadang-kadang kelas akan menjadi jenis beberapa kelas yang berbeda, dan kadang-kadang penting untuk mencerminkan hal ini.

"Mixin" juga terkadang bermanfaat. Mereka umumnya kelas kecil, biasanya tidak mewarisi dari apa pun, menyediakan fungsionalitas yang bermanfaat.

Selama hierarki warisan cukup dangkal (sebagaimana seharusnya selalu terjadi), dan dikelola dengan baik, Anda tidak mungkin mendapatkan warisan berlian yang ditakuti. Intan tidak menjadi masalah dengan semua bahasa yang menggunakan banyak pewarisan, tetapi perlakuan C ++ terhadapnya sering kali canggung dan terkadang membingungkan.

Sementara saya mengalami kasus di mana pewarisan berganda sangat berguna, mereka sebenarnya cukup langka. Ini mungkin karena saya lebih suka menggunakan metode desain lain ketika saya tidak benar-benar membutuhkan banyak pewarisan. Saya lebih suka menghindari konstruksi bahasa yang membingungkan, dan mudah untuk membuat kasus warisan di mana Anda harus membaca manual dengan sangat baik untuk mencari tahu apa yang terjadi.


6

Anda seharusnya tidak "menghindari" pewarisan berganda tetapi Anda harus menyadari masalah yang dapat muncul seperti 'masalah berlian' ( http://en.wikipedia.org/wiki/Diamond_problem ) dan memperlakukan kekuatan yang diberikan kepada Anda dengan hati-hati. , sebagaimana mestinya dengan semua kekuatan.


1
+1: Sama seperti Anda seharusnya tidak menghindari petunjuk; Anda hanya perlu menyadari konsekuensi mereka. Orang-orang perlu berhenti mengoceh frasa (seperti "MI itu jahat", "jangan optimalkan", "goto itu jahat") mereka telah belajar dari internet hanya karena beberapa hippy mengatakan itu buruk bagi kebersihan mereka. Bagian terburuk adalah bahwa mereka bahkan tidak pernah mencoba menggunakan hal-hal seperti itu dan menganggapnya seolah-olah diucapkan dari Alkitab.
Thomas Eding

1
Saya setuju. Jangan "menghindarinya", tetapi ketahuilah cara terbaik untuk mengatasi masalah tersebut. Mungkin saya lebih suka menggunakan sifat komposisi, tetapi C ++ tidak memilikinya. Mungkin saya lebih suka menggunakan delegasi otomatis 'pegangan', tetapi C ++ tidak memilikinya. Jadi saya menggunakan alat MI umum dan tumpul untuk beberapa tujuan yang berbeda, mungkin secara bersamaan.
JDługosz

3

Dengan risiko menjadi sedikit abstrak, saya merasa terpikir untuk berpikir tentang pewarisan dalam kerangka teori kategori.

Jika kita memikirkan semua kelas dan panah di antara mereka yang menunjukkan hubungan warisan, maka kira-kira seperti ini

A --> B

berarti class Bberasal dari class A. Perhatikan itu, diberikan

A --> B, B --> C

kita katakan C berasal dari B yang berasal dari A, jadi C juga dikatakan berasal dari A, jadi

A --> C

Selain itu, kami mengatakan bahwa untuk setiap kelas Ayang Aberasal dari sepele A, dengan demikian model warisan kami memenuhi definisi kategori. Dalam bahasa yang lebih tradisional, kami memiliki kategori Classdengan objek semua kelas dan morfisme hubungan warisan.

Itu sedikit pengaturan, tetapi dengan itu mari kita lihat Diamond of Doom kami:

C --> D
^     ^
|     |
A --> B

Ini adalah diagram yang terlihat teduh, tetapi akan berhasil. Jadi Dmewarisi dari semua A, B, dan C. Lebih jauh, dan semakin dekat dengan menjawab pertanyaan OP, Djuga mewarisi dari superclass manapun A. Kita bisa menggambar diagram

C --> D --> R
^     ^
|     |
A --> B
^ 
|
Q

Sekarang, masalah yang terkait dengan Diamond of Death di sini adalah kapan Cdan Bberbagi beberapa properti / nama metode dan hal-hal menjadi ambigu; Namun, jika kita memindahkan perilaku bersama apa pun Amaka ambiguitas menghilang.

Masukkan dalam kategori kategoris, kami ingin A, Bdan Cmenjadi seperti itu jika Bdan Cmewarisi dari Qitu Adapat ditulis ulang sebagai subkelas dari Q. Ini membuat Asesuatu yang disebut pushout .

Ada juga konstruksi simetris yang Ddisebut pullback . Ini pada dasarnya adalah kelas berguna paling umum yang bisa Anda buat yang mewarisi dari keduanya Bdan C. Artinya, jika Anda memiliki kelas lain yang Rdiwarisi dari Bdan C, maka Dadalah kelas di mana Rdapat ditulis ulang sebagai subkelas dari D.

Memastikan tip berlian Anda adalah mundur dan pushouts memberi kami cara yang bagus untuk secara umum menangani masalah perselisihan nama atau pemeliharaan yang mungkin timbul sebaliknya.

Catatan paercebal 's jawaban terinspirasi ini sebagai nasihat-nasihatnya yang tersirat oleh model di atas mengingat bahwa kita bekerja dalam kategori penuh Kelas dari semua kelas yang mungkin.

Saya ingin menggeneralisasikan argumennya pada sesuatu yang menunjukkan betapa rumitnya hubungan pewarisan berganda yang kuat dan tidak bermasalah.

TL; DR Pikirkan hubungan warisan dalam program Anda sebagai membentuk kategori. Kemudian Anda dapat menghindari masalah Diamond of Doom dengan membuat pushout kelas yang diwariskan multiply dan simetris, membuat kelas induk yang merupakan pullback.


3

Kami menggunakan Eiffel. Kami memiliki MI yang sangat baik. Jangan khawatir. Tidak ada masalah. Mudah dikelola. Ada kalanya TIDAK menggunakan MI. Namun, ini lebih berguna daripada yang disadari orang karena mereka adalah: A) dalam bahasa berbahaya yang tidak mengelolanya dengan baik - ATAU - B) puas dengan bagaimana mereka telah bekerja di sekitar MI selama bertahun-tahun -OR- C) alasan lain ( terlalu banyak untuk dicantumkan, saya cukup yakin - lihat jawaban di atas).

Bagi kami, menggunakan Eiffel, MI sama wajarnya dengan yang lain dan alat halus lainnya di kotak alat. Terus terang, kami cukup khawatir bahwa tidak ada orang lain yang menggunakan Eiffel. Jangan khawatir. Kami senang dengan apa yang kami miliki dan mengundang Anda untuk melihatnya.

Saat Anda mencari: Catat keamanan Void dan pemberantasan dudukan referensi Null. Sementara kita semua menari di sekitar MI, petunjuk Anda hilang! :-)


2

Setiap bahasa pemrograman memiliki perlakuan yang sedikit berbeda dari pemrograman berorientasi objek dengan pro dan kontra. Versi C ++ menempatkan penekanan tepat pada kinerja dan memiliki kelemahan yang menyertainya adalah sangat mudah untuk menulis kode yang tidak valid - dan ini berlaku untuk multiple inheritance. Sebagai akibatnya ada kecenderungan untuk menjauhkan programmer dari fitur ini.

Orang lain telah menjawab pertanyaan tentang apakah warisan ganda itu tidak baik. Tetapi kita telah melihat beberapa komentar yang sedikit banyak menyiratkan bahwa alasan untuk menghindarinya adalah karena itu tidak aman. Ya dan tidak.

Seperti yang sering benar dalam C ++, jika Anda mengikuti pedoman dasar Anda dapat menggunakannya dengan aman tanpa harus "melihat-lihat" Anda terus-menerus. Ide kuncinya adalah Anda membedakan jenis definisi kelas khusus yang disebut "campuran"; kelas adalah campuran jika semua fungsi anggotanya adalah virtual (atau virtual murni). Kemudian Anda diizinkan untuk mewarisi dari satu kelas utama dan sebanyak "campuran" yang Anda suka - tetapi Anda harus mewarisi mixin dengan kata kunci "virtual". misalnya

class CounterMixin {
    int count;
public:
    CounterMixin() : count( 0 ) {}
    virtual ~CounterMixin() {}
    virtual void increment() { count += 1; }
    virtual int getCount() { return count; }
};

class Foo : public Bar, virtual public CounterMixin { ..... };

Saran saya adalah bahwa jika Anda berniat menggunakan kelas sebagai kelas campuran, Anda juga mengadopsi konvensi penamaan untuk memudahkan siapa pun membaca kode untuk melihat apa yang terjadi & untuk memverifikasi Anda bermain dengan aturan pedoman dasar . Dan Anda akan menemukannya bekerja lebih baik jika mix-in Anda memiliki konstruktor default juga, hanya karena cara kerja kelas dasar virtual. Dan ingat untuk membuat semua destructor virtual juga.

Perhatikan bahwa penggunaan kata "mix-in" di sini tidak sama dengan kelas template parameterised (lihat tautan ini untuk penjelasan yang baik) tapi saya pikir ini adalah penggunaan terminologi yang adil.

Sekarang saya tidak ingin memberi kesan bahwa ini adalah satu-satunya cara untuk menggunakan banyak warisan secara aman. Hanya satu cara yang cukup mudah untuk diperiksa.


2

Anda harus menggunakannya dengan hati-hati, ada beberapa kasus, seperti Masalah Berlian , ketika segala sesuatunya menjadi rumit.

teks alternatif
(sumber: learncpp.com )


12
Ini adalah contoh yang buruk, karena mesin fotokopi harus terdiri dari pemindai dan printer, bukan warisan darinya.
Svante

2
Lagi pula, yang Printerseharusnya tidak menjadi PoweredDevice. A Printeruntuk pencetakan, bukan manajemen daya. Implementasi printer tertentu mungkin harus melakukan beberapa manajemen daya, tetapi perintah manajemen daya ini tidak boleh diekspos langsung ke pengguna printer. Saya tidak bisa membayangkan penggunaan hirarki ini di dunia nyata.
curiousguy


1

Di luar pola intan, multiple inheritance cenderung membuat model objek lebih sulit untuk dipahami, yang pada gilirannya meningkatkan biaya perawatan.

Komposisi secara intrinsik mudah dipahami, dipahami, dan dijelaskan. Ini bisa membosankan untuk menulis kode, tetapi IDE yang bagus (sudah beberapa tahun sejak saya bekerja dengan Visual Studio, tapi tentu saja Java IDEs semuanya memiliki alat pengotomatisasi cara pintas komposisi yang hebat) harus membantu Anda mengatasi rintangan itu.

Juga, dalam hal pemeliharaan, "masalah intan" muncul dalam contoh turunan non-literal juga. Misalnya, jika Anda memiliki A dan B dan kelas C Anda memperpanjang keduanya, dan A memiliki metode 'makeJuice' yang membuat jus jeruk dan Anda mengembangkannya untuk membuat jus jeruk dengan twist jeruk nipis: apa yang terjadi ketika perancang untuk ' B 'menambahkan metode' makeJuice 'yang menghasilkan dan arus listrik? 'A' dan 'B' mungkin kompatibel dengan "orang tua" saat ini , tetapi itu tidak berarti mereka akan selalu seperti itu!

Secara keseluruhan, pepatah cenderung untuk menghindari warisan, dan terutama pewarisan berganda, adalah sehat. Seperti semua maksim, ada pengecualian, tetapi Anda perlu memastikan bahwa ada tanda neon hijau berkedip yang menunjuk pada setiap pengecualian yang Anda kode (dan latih otak Anda sehingga setiap kali Anda melihat pohon warisan seperti itu, Anda menggambar di neon hijau berkedip Anda sendiri tanda), dan Anda memeriksa untuk memastikan semuanya masuk akal sesekali.


what happens when the designer for 'B' adds a 'makeJuice' method which generates and electrical current?Uhhh, Anda mendapatkan kesalahan kompilasi tentu saja (jika penggunaannya ambigu).
Thomas Eding

1

Masalah utama dengan MI objek konkret adalah bahwa Anda jarang memiliki objek yang secara sah harus "Menjadi A DAN menjadi B", sehingga jarang solusi yang tepat berdasarkan alasan logis. Jauh lebih sering, Anda memiliki objek C yang menaati "C dapat bertindak sebagai A atau B", yang dapat Anda capai melalui warisan antarmuka & komposisi. Tetapi jangan membuat kesalahan - warisan dari beberapa antarmuka masih MI, hanya sebagian dari itu.

Khususnya untuk C ++, kelemahan utama dari fitur ini bukanlah EXISTENCE aktual dari Multiple Inheritance, tetapi beberapa konstruksi yang memungkinkannya hampir selalu cacat. Misalnya, mewarisi banyak salinan dari objek yang sama seperti:

class B : public A, public A {};

cacat oleh DEFINISI. Diterjemahkan ke dalam bahasa Inggris ini adalah "B adalah A dan a". Jadi, bahkan dalam bahasa manusia ada ambiguitas yang parah. Apakah maksud Anda "B memiliki 2 As" atau hanya "B adalah A"? Mengizinkan kode patologis seperti itu, dan yang lebih buruk menjadikannya contoh penggunaan, tidak menyukai C ++ ketika membuat kasus untuk mempertahankan fitur dalam bahasa penerus.


0

Anda dapat menggunakan komposisi dalam preferensi untuk warisan.

Perasaan umum adalah komposisi itu lebih baik, dan itu sangat baik dibahas.


4
-1: The general feeling is that composition is better, and it's very well discussed.Itu tidak berarti komposisi lebih baik.
Thomas Eding

Kecuali itu, sebenarnya, lebih baik.
Ali Afshar

12
Kecuali untuk kasus-kasus di mana itu tidak lebih baik.
Thomas Eding

0

dibutuhkan 4/8 byte per kelas yang terlibat. (Satu pointer ini per kelas).

Ini mungkin tidak akan pernah menjadi perhatian, tetapi jika suatu hari Anda memiliki struktur data mikro yang dikeluarkan miliaran waktu, itu akan menjadi.


1
Yakin tentang itu? Biasanya, setiap warisan serius akan membutuhkan pointer di setiap anggota kelas, tetapi apakah itu perlu skala dengan kelas yang diwarisi? Lebih penting lagi, berapa kali Anda memiliki miliaran struktur data kecil?
David Thornley

3
@ Davidvidhorn " berapa kali Anda memiliki miliaran struktur data kecil? " Ketika Anda membuat argumen melawan MI.
curiousguy
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.