Mengapa lebih suka komposisi daripada warisan? Imbalan apa yang ada untuk setiap pendekatan? Kapan sebaiknya Anda memilih warisan daripada komposisi?
language-agnostic
oop
inheritance
composition
aggregation
Hanya baca
sumber
sumber
Jawaban:
Lebih suka komposisi daripada pewarisan karena lebih mudah ditempa / mudah dimodifikasi nanti, tetapi jangan gunakan pendekatan selalu-buat. Dengan komposisi, mudah untuk mengubah perilaku saat bepergian dengan Dependency Injection / Setters. Warisan lebih kaku karena sebagian besar bahasa tidak memungkinkan Anda untuk berasal dari lebih dari satu jenis. Jadi angsa lebih atau kurang dimasak setelah Anda berasal dari TypeA.
Tes asam saya untuk di atas adalah:
Apakah TypeB ingin mengekspos antarmuka lengkap (semua metode publik tidak kurang) dari TypeA sehingga TypeB dapat digunakan di mana TypeA diharapkan? Menunjukkan Warisan .
Apakah TypeB hanya menginginkan sebagian / bagian dari perilaku yang diekspos oleh TypeA? Menunjukkan perlunya Komposisi.
Pembaruan: Baru saja kembali ke jawaban saya dan tampaknya sekarang tidak lengkap tanpa menyebutkan secara spesifik Prinsip Pergantian Liskov dari Barbara Liskov sebagai ujian untuk 'Haruskah saya mewarisi dari jenis ini?'
sumber
Pikirkan penahanan sebagai memiliki hubungan. Mobil "memiliki" mesin, seseorang "memiliki" nama, dll.
Pikirkan warisan sebagai adalah hubungan. Mobil "adalah" kendaraan, seseorang "adalah" mamalia, dll.
Saya tidak mengambil kredit untuk pendekatan ini. Saya mengambilnya langsung dari Edisi Kedua Kode Lengkap oleh Steve McConnell , Bagian 6.3 .
sumber
Jika Anda memahami perbedaannya, lebih mudah untuk dijelaskan.
Kode Prosedural
Contohnya adalah PHP tanpa menggunakan kelas (khususnya sebelum PHP5). Semua logika dikodekan dalam satu set fungsi. Anda dapat memasukkan file-file lain yang mengandung fungsi pembantu dan sebagainya dan melakukan logika bisnis Anda dengan meneruskan data ke berbagai fungsi. Ini bisa sangat sulit untuk dikelola ketika aplikasi tumbuh. PHP5 mencoba untuk memperbaiki ini dengan menawarkan desain yang lebih berorientasi objek.
Warisan
Ini mendorong penggunaan kelas. Warisan adalah salah satu dari tiga prinsip desain OO (pewarisan, polimorfisme, enkapsulasi).
Ini adalah warisan di tempat kerja. Karyawan "adalah" Orang atau warisan dari Orang. Semua hubungan pewarisan adalah hubungan "is-a". Karyawan juga membayangi properti Judul dari Orang, artinya Karyawan. Judul akan mengembalikan Judul untuk Karyawan, bukan Orang.
Komposisi
Komposisi lebih disukai daripada warisan. Sederhananya Anda harus:
Komposisi biasanya "memiliki" atau "menggunakan hubungan". Di sini kelas Karyawan memiliki Seseorang. Itu tidak mewarisi dari Orang tetapi mendapatkan objek Orang diteruskan kepadanya, itulah sebabnya ia "memiliki" Orang.
Komposisi atas Warisan
Sekarang katakan Anda ingin membuat jenis Manajer sehingga Anda berakhir dengan:
Contoh ini akan berfungsi dengan baik, bagaimana jika Orang dan Karyawan keduanya menyatakan
Title
? Haruskah Manajer. Judul mengembalikan "Manajer Operasi" atau "Tuan"? Dalam komposisi ambiguitas ini lebih baik ditangani:Objek Manajer disusun sebagai Karyawan dan Orang. Perilaku Judul diambil dari karyawan. Komposisi eksplisit ini menghilangkan ambiguitas antara lain dan Anda akan menemukan lebih sedikit bug.
sumber
Dengan semua manfaat tak terbantahkan yang diberikan oleh warisan, inilah beberapa kelemahannya.
Kerugian Warisan:
Di sisi lain, komposisi objek didefinisikan pada saat runtime melalui objek yang memperoleh referensi ke objek lain. Dalam kasus seperti itu objek-objek ini tidak akan pernah dapat mencapai data yang dilindungi satu sama lain (tidak ada istirahat enkapsulasi) dan akan dipaksa untuk menghormati antarmuka masing-masing. Dan dalam hal ini juga, dependensi implementasi akan jauh lebih sedikit daripada dalam kasus pewarisan.
sumber
Alasan lain, sangat pragmatis, untuk lebih memilih komposisi daripada warisan berkaitan dengan model domain Anda, dan memetakannya ke database relasional. Sangat sulit untuk memetakan pewarisan ke model SQL (Anda berakhir dengan semua jenis solusi peretasan, seperti membuat kolom yang tidak selalu digunakan, menggunakan tampilan, dll). Beberapa ORML mencoba menangani ini, tetapi selalu menjadi rumit dengan cepat. Komposisi dapat dengan mudah dimodelkan melalui hubungan kunci asing antara dua tabel, tetapi warisan jauh lebih sulit.
sumber
Sementara dalam kata-kata singkat saya akan setuju dengan "Lebih suka komposisi daripada warisan", sangat sering bagi saya itu terdengar seperti "lebih suka kentang daripada coca-cola". Ada tempat untuk warisan dan tempat untuk komposisi. Anda perlu memahami perbedaan, maka pertanyaan ini akan hilang. Apa artinya bagi saya adalah "jika Anda akan menggunakan warisan - pikirkan lagi, kemungkinan Anda perlu komposisi".
Anda harus memilih kentang daripada coca cola saat Anda ingin makan, dan coca cola daripada kentang ketika Anda ingin minum.
Membuat subkelas harus berarti lebih dari sekadar cara yang nyaman untuk memanggil metode superclass. Anda harus menggunakan pewarisan ketika subclass "is-a" kelas super baik secara struktural dan fungsional, ketika itu dapat digunakan sebagai superclass dan Anda akan menggunakannya. Jika bukan itu masalahnya - itu bukan warisan, tetapi sesuatu yang lain. Komposisi adalah ketika objek Anda terdiri dari yang lain, atau memiliki hubungan dengan mereka.
Jadi bagi saya kelihatannya jika seseorang tidak tahu apakah dia membutuhkan warisan atau komposisi, masalah sebenarnya adalah dia tidak tahu apakah dia ingin minum atau makan. Pikirkan domain masalah Anda lebih lanjut, pahami dengan lebih baik.
sumber
InternalCombustionEngine
dengan kelas turunanGasolineEngine
. Yang terakhir menambahkan hal-hal seperti busi, yang tidak dimiliki oleh kelas dasar, tetapi menggunakan hal itu sebagai bilahInternalCombustionEngine
akan menyebabkan busi digunakan.Warisan cukup menarik terutama yang berasal dari tanah prosedural dan sering terlihat elegan. Maksud saya semua yang perlu saya lakukan adalah menambahkan sedikit fungsionalitas ini ke beberapa kelas lain, kan? Nah, salah satu masalahnya adalah itu
warisan mungkin merupakan bentuk penggabungan terburuk yang Anda miliki
Kelas dasar Anda memecah enkapsulasi dengan mengekspos detail implementasi ke subclass dalam bentuk anggota yang dilindungi. Ini membuat sistem Anda kaku dan rapuh. Namun kelemahan yang lebih tragis adalah subclass baru membawa semua bagasi dan pendapat dari rantai pewarisan.
Artikel, Inheritance is Evil: The Epic Fail dari DataAnnotationsModelBinder , membahas contoh ini dalam C #. Ini menunjukkan penggunaan pewarisan ketika komposisi seharusnya digunakan dan bagaimana itu bisa direactored.
sumber
Dalam Java atau C #, objek tidak dapat mengubah tipenya setelah instantiated.
Jadi, jika objek Anda perlu tampil sebagai objek yang berbeda atau berperilaku berbeda tergantung pada keadaan atau kondisi objek, maka gunakan Komposisi : Lihat Pola Desain Negara dan Strategi .
Jika objek harus berjenis sama, maka gunakan Inheritance atau implement interfaces.
sumber
Client
. Kemudian, muncul konsep baru yangPreferredClient
muncul kemudian. HaruskahPreferredClient
mewarisiClient
? Klien yang lebih disukai adalah klien, toh? Ya, tidak terlalu cepat ... seperti yang Anda katakan objek tidak dapat mengubah kelasnya saat runtime. Bagaimana Anda membuat modelclient.makePreferred()
operasi? Mungkin jawabannya terletak pada penggunaan komposisi dengan konsep yang hilang,Account
mungkin?Client
kelas yang berbeda , mungkin hanya ada satu yang merangkum konsepAccount
yang bisa menjadiStandardAccount
atauPreferredAccount
...Tidak menemukan jawaban yang memuaskan di sini, jadi saya menulis yang baru.
Untuk memahami mengapa " lebih memilih komposisi daripada pewarisan", pertama-tama kita perlu mendapatkan kembali asumsi yang dihilangkan dalam idiom singkat ini.
Ada dua manfaat warisan: subtyping dan subclassing
Subtyping berarti menyesuaikan diri dengan tanda tangan jenis (antarmuka), yaitu satu set API, dan satu dapat menimpa bagian dari tanda tangan untuk mencapai polimorfisme subtyping.
Subclassing berarti penggunaan kembali implisit implementasi metode.
Dengan dua manfaat tersebut muncul dua tujuan berbeda untuk melakukan pewarisan: berorientasi subtyping dan berorientasi penggunaan kembali kode.
Jika penggunaan kembali kode adalah satu - satunya tujuan, subclassing dapat memberikan satu lebih dari apa yang dia butuhkan, yaitu beberapa metode publik dari kelas induk tidak masuk akal untuk kelas anak. Dalam hal ini, alih-alih lebih menyukai komposisi daripada warisan, komposisi dituntut . Di sinilah pula gagasan "is-a" vs. "has-a" berasal.
Jadi hanya ketika subtyping dimaksudkan, yaitu untuk menggunakan kelas baru nanti secara polimorfik, kita menghadapi masalah dalam memilih pewarisan atau komposisi. Ini adalah asumsi yang dihilangkan dalam idiom singkat yang sedang dibahas.
Untuk subtipe adalah untuk menyesuaikan dengan tanda tangan jenis, ini berarti komposisi harus selalu mengekspos jumlah API yang tidak sedikit. Sekarang trade off menendang:
Warisan menyediakan penggunaan kembali kode langsung jika tidak diganti, sementara komposisi harus mengode ulang setiap API, bahkan jika itu hanya pekerjaan delegasi yang sederhana.
Warisan menyediakan rekursi terbuka langsung melalui situs polimorfik internal
this
, yaitu menggunakan metode override (atau tipe genap ) dalam fungsi anggota lain, baik publik atau pribadi (meskipun tidak disarankan ). Rekursi terbuka dapat disimulasikan melalui komposisi , tetapi membutuhkan usaha ekstra dan mungkin tidak selalu layak (?). Ini jawaban untuk pertanyaan digandakan berbicara sesuatu yang serupa.Warisan memperlihatkan anggota yang dilindungi . Ini memecah enkapsulasi dari kelas induk, dan jika digunakan oleh subkelas, ketergantungan lain antara anak dan orang tuanya diperkenalkan.
Komposisi memiliki kecocokan inversi kontrol, dan ketergantungannya dapat disuntikkan secara dinamis, seperti yang ditunjukkan dalam pola dekorator dan pola proksi .
Komposisi memiliki manfaat pemrograman berorientasi kombinator , yaitu bekerja dengan cara seperti pola komposit .
Komposisi segera mengikuti pemrograman ke antarmuka .
Komposisi memiliki manfaat pewarisan berganda yang mudah .
Dengan mempertimbangkan trade off di atas, kami karenanya lebih memilih komposisi daripada warisan. Namun untuk kelas yang terkait erat, yaitu ketika penggunaan kembali kode implisit benar-benar membuat manfaat, atau kekuatan magis rekursi terbuka diinginkan, pewarisan akan menjadi pilihan.
sumber
Secara pribadi saya belajar untuk selalu lebih suka komposisi daripada warisan. Tidak ada masalah terprogram yang dapat Anda pecahkan dengan pewarisan yang tidak dapat Anda pecahkan dengan komposisi; meskipun Anda mungkin harus menggunakan Antarmuka (Java) atau Protokol (Obj-C) dalam beberapa kasus. Karena C ++ tidak tahu hal seperti itu, Anda harus menggunakan kelas basis abstrak, yang berarti Anda tidak bisa sepenuhnya menghilangkan warisan dalam C ++.
Komposisi seringkali lebih logis, memberikan abstraksi yang lebih baik, enkapsulasi yang lebih baik, penggunaan kembali kode yang lebih baik (terutama dalam proyek yang sangat besar) dan kecil kemungkinannya untuk memecahkan apa pun dari kejauhan hanya karena Anda membuat perubahan terisolasi di mana saja dalam kode Anda. Ini juga memudahkan untuk menegakkan " Prinsip Tanggung Jawab Tunggal ", yang sering diringkas sebagai " Seharusnya tidak ada lebih dari satu alasan bagi suatu kelas untuk berubah. ", Dan itu berarti bahwa setiap kelas ada untuk tujuan tertentu dan itu harus hanya memiliki metode yang terkait langsung dengan tujuannya. Juga memiliki pohon warisan yang sangat dangkal membuatnya lebih mudah untuk menjaga ikhtisar bahkan ketika proyek Anda mulai menjadi sangat besar. Banyak orang berpikir bahwa warisan mewakili dunia nyata kitacukup bagus, tapi itu tidak benar. Dunia nyata menggunakan komposisi jauh lebih banyak daripada warisan. Hampir setiap objek dunia nyata yang dapat Anda pegang di tangan Anda telah tersusun dari objek dunia nyata lainnya yang lebih kecil.
Namun, ada kekurangan komposisi. Jika Anda melewatkan warisan sama sekali dan hanya fokus pada komposisi, Anda akan melihat bahwa Anda sering harus menulis beberapa baris kode tambahan yang tidak diperlukan jika Anda telah menggunakan warisan. Anda juga kadang-kadang dipaksa untuk mengulangi sendiri dan ini melanggar Prinsip KERING(KERING = Jangan Ulangi Diri Sendiri). Komposisi juga sering membutuhkan pendelegasian, dan suatu metode hanya memanggil metode lain dari objek lain tanpa kode lain di sekitar panggilan ini. "Pemanggilan metode ganda" semacam itu (yang mungkin dengan mudah mencakup pemanggilan metode tripel atau empat kali lipat dan bahkan lebih jauh dari itu) memiliki kinerja yang jauh lebih buruk daripada pewarisan, di mana Anda cukup mewarisi metode orang tua Anda. Memanggil metode yang diwarisi mungkin sama cepatnya dengan memanggil metode yang tidak diwarisi, atau mungkin sedikit lebih lambat, tetapi biasanya masih lebih cepat dari dua panggilan metode yang berurutan.
Anda mungkin telah memperhatikan bahwa sebagian besar bahasa OO tidak mengizinkan banyak pewarisan. Meskipun ada beberapa kasus di mana pewarisan berganda benar-benar dapat membelikan Anda sesuatu, tetapi itu lebih merupakan pengecualian daripada aturan. Setiap kali Anda mengalami situasi di mana Anda berpikir "pewarisan berganda akan menjadi fitur yang sangat keren untuk menyelesaikan masalah ini", Anda biasanya berada pada titik di mana Anda harus memikirkan kembali warisan secara keseluruhan, karena bahkan mungkin memerlukan beberapa baris kode tambahan , solusi berdasarkan komposisi biasanya akan menjadi jauh lebih elegan, fleksibel dan bukti masa depan.
Warisan adalah fitur yang sangat keren, tapi saya khawatir ini sudah terlalu sering digunakan beberapa tahun terakhir. Orang memperlakukan warisan sebagai satu palu yang dapat memakukan semuanya, terlepas dari apakah itu benar-benar paku, sekrup, atau mungkin sesuatu yang sama sekali berbeda.
sumber
TextFile
adalah aFile
.Aturan umum saya: Sebelum menggunakan warisan, pertimbangkan apakah komposisi lebih masuk akal.
Alasan: Subkelas biasanya berarti lebih banyak kerumitan dan keterhubungan, yaitu lebih sulit untuk diubah, dipertahankan, dan diukur tanpa membuat kesalahan.
Jawaban yang jauh lebih lengkap dan konkret dari Tim Boudreau of Sun:
sumber
Mengapa lebih suka komposisi daripada warisan?
Lihat jawaban lain.
Kapan Anda bisa menggunakan warisan?
Sering dikatakan bahwa kelas
Bar
dapat mewarisi kelasFoo
ketika kalimat berikut ini benar:Sayangnya, tes di atas saja tidak dapat diandalkan. Gunakan yang berikut ini sebagai gantinya:
Memastikan tes pertama bahwa semua getter dari
Foo
masuk akal diBar
(= sifat bersama), sedangkan tes kedua memastikan bahwa semua setter dariFoo
masuk akal diBar
(fungsi = bersama).Contoh 1: Anjing -> Hewan
Seekor anjing adalah binatang DAN anjing dapat melakukan segala sesuatu yang dapat dilakukan binatang (seperti bernapas, sekarat, dll.). Oleh karena itu, kelas
Dog
dapat mewarisi kelas tersebutAnimal
.Contoh 2: Lingkaran - / -> Elips
Lingkaran adalah elips TETAP lingkaran tidak bisa melakukan semua yang bisa dilakukan elips. Misalnya, lingkaran tidak bisa meregang, sedangkan elips bisa. Oleh karena itu, kelas
Circle
tidak dapat mewarisi kelasEllipse
.Ini disebut masalah Circle-Ellipse , yang sebenarnya bukan masalah, hanya bukti yang jelas bahwa tes pertama saja tidak cukup untuk menyimpulkan bahwa pewarisan itu mungkin. Secara khusus, contoh ini menyoroti bahwa kelas turunan harus memperluas fungsi kelas dasar, jangan pernah batasi . Jika tidak, kelas dasar tidak dapat digunakan secara polimorfik.
Kapan Anda harus menggunakan warisan?
Bahkan jika Anda dapat menggunakan warisan bukan berarti Anda harus : menggunakan komposisi selalu merupakan pilihan. Warisan adalah alat yang ampuh yang memungkinkan penggunaan kembali kode implisit dan pengiriman dinamis, tetapi ia datang dengan beberapa kelemahan, itulah sebabnya komposisi sering lebih disukai. Pertukaran antara warisan dan komposisi tidak jelas, dan menurut saya paling baik dijelaskan dalam jawaban lcn .
Sebagai aturan praktis, saya cenderung memilih pewarisan daripada komposisi ketika penggunaan polimorfik diharapkan menjadi sangat umum, dalam hal ini kekuatan pengiriman dinamis dapat mengarah ke API yang jauh lebih mudah dibaca dan elegan. Misalnya, memiliki kelas polimorfik
Widget
dalam kerangka kerja GUI, atau kelas polimorfikNode
di pustaka XML memungkinkan untuk memiliki API yang jauh lebih mudah dibaca dan intuitif untuk digunakan daripada apa yang akan Anda miliki dengan solusi murni berdasarkan komposisi.Prinsip Pergantian Liskov
Asal tahu saja, metode lain yang digunakan untuk menentukan apakah pewarisan itu mungkin disebut Prinsip Substitusi Liskov :
Pada dasarnya, ini berarti bahwa pewarisan adalah mungkin jika kelas dasar dapat digunakan secara polimorfik, yang saya percaya setara dengan pengujian kami "sebuah bar adalah foo dan bar dapat melakukan semua yang dapat dilakukan foo".
sumber
computeArea(Circle* c) { return pi * square(c->radius()); }
. Jelas rusak jika melewati Ellipse (apa arti radius () bahkan artinya?). Ellipse bukan Circle, dan karena itu seharusnya tidak berasal dari Circle.computeArea(Circle *c) { return pi * width * height / 4.0; }
Sekarang generik.width()
danheight()
? Bagaimana jika sekarang pengguna perpustakaan memutuskan untuk membuat kelas lain yang disebut "EggShape"? Haruskah itu juga berasal dari "Lingkaran"? Tentu saja tidak. Bentuk telur bukan lingkaran, dan elips juga bukan lingkaran, jadi tidak ada yang berasal dari Lingkaran karena itu menghancurkan LSP. Metode yang melakukan operasi pada kelas Circle * membuat asumsi kuat tentang apa lingkaran itu, dan melanggar asumsi ini hampir pasti akan menyebabkan bug.Warisan sangat kuat, tetapi Anda tidak bisa memaksanya (lihat: masalah lingkaran-elips ). Jika Anda benar-benar tidak dapat sepenuhnya yakin tentang hubungan subtipe "is-a" yang sebenarnya, maka yang terbaik adalah memilih komposisi.
sumber
Warisan menciptakan hubungan yang kuat antara subclass dan kelas super; subclass harus mengetahui detail implementasi super class. Menciptakan kelas super jauh lebih sulit, ketika Anda harus berpikir tentang bagaimana itu dapat diperluas. Anda harus mendokumentasikan invarian kelas dengan hati-hati, dan nyatakan apa metode lain yang bisa digunakan metode yang dapat ditimpa secara internal.
Warisan kadang berguna, jika hirarki benar-benar mewakili hubungan is-a-a. Ini terkait dengan Prinsip Terbuka-Tertutup, yang menyatakan bahwa kelas harus ditutup untuk modifikasi tetapi terbuka untuk ekstensi. Dengan begitu Anda dapat memiliki polimorfisme; untuk memiliki metode generik yang berhubungan dengan tipe super dan metodenya, tetapi melalui pengiriman dinamis metode subclass dipanggil. Ini fleksibel, dan membantu menciptakan tipuan, yang sangat penting dalam perangkat lunak (untuk mengetahui lebih sedikit tentang detail implementasi).
Warisan mudah digunakan secara berlebihan, dan menciptakan kompleksitas tambahan, dengan ketergantungan antar kelas. Juga memahami apa yang terjadi selama pelaksanaan program menjadi sangat sulit karena lapisan dan pemilihan metode panggilan yang dinamis.
Saya akan menyarankan menggunakan menulis sebagai default. Ini lebih modular, dan memberikan manfaat ikatan yang lebih lambat (Anda dapat mengubah komponen secara dinamis). Juga lebih mudah untuk menguji hal-hal secara terpisah. Dan jika Anda perlu menggunakan metode dari kelas, Anda tidak dipaksa dari bentuk tertentu (Prinsip Pergantian Liskov).
sumber
Inheritance is sometimes useful... That way you can have polymorphism
sebagai penghubung keras konsep-konsep pewarisan dan polimorfisme (subtyping diasumsikan diberikan konteksnya). Komentar saya dimaksudkan untuk menunjukkan apa yang Anda klarifikasi dalam komentar Anda: bahwa warisan bukan satu-satunya cara untuk menerapkan polimorfisme, dan pada kenyataannya tidak selalu menjadi faktor penentu ketika memutuskan antara komposisi dan warisan.Misalkan pesawat hanya memiliki dua bagian: mesin dan sayap.
Lalu ada dua cara mendesain kelas pesawat terbang.
Sekarang pesawat Anda dapat mulai dengan memiliki sayap tetap
dan mengubahnya menjadi sayap putar dengan cepat. Ini pada dasarnya
mesin dengan sayap. Tetapi bagaimana jika saya ingin mengganti
mesin dengan cepat juga?
Entah kelas dasar
Engine
memperlihatkan mutator untuk mengubahpropertinya, atau saya mendesain ulang
Aircraft
sebagai:Sekarang, saya dapat mengganti mesin saya dengan cepat juga.
sumber
Anda harus memiliki melihat The Liskov Pergantian Prinsip di Paman Bob SOLID prinsip-prinsip desain kelas. :)
sumber
Saat Anda ingin "menyalin" / Mengekspos API kelas dasar, Anda menggunakan warisan. Saat Anda hanya ingin "menyalin" fungsionalitas, gunakan delegasi.
Salah satu contohnya: Anda ingin membuat Stack out of a List. Stack hanya memiliki pop, push, dan intip. Anda tidak boleh menggunakan pewarisan karena Anda tidak ingin fungsi push_back, push_front, removeAt, dkk. Di Stack.
sumber
Dua cara ini dapat hidup bersama dengan baik dan benar-benar saling mendukung.
Komposisi hanya memainkannya modular: Anda membuat antarmuka yang mirip dengan kelas induk, membuat objek baru dan mendelegasikan panggilan ke sana. Jika benda-benda ini tidak perlu saling mengenal, itu cukup aman dan mudah digunakan komposisi. Ada banyak kemungkinan di sini.
Namun, jika kelas induk karena alasan tertentu perlu mengakses fungsi yang disediakan oleh "kelas anak" untuk programmer yang tidak berpengalaman, itu mungkin terlihat seperti tempat yang bagus untuk menggunakan warisan. Kelas induk hanya dapat memanggil abstrak itu sendiri "foo ()" yang ditimpa oleh subkelas dan kemudian dapat memberikan nilai ke basis abstrak.
Sepertinya ide yang bagus, tetapi dalam banyak kasus lebih baik hanya memberi kelas objek yang mengimplementasikan foo () (atau bahkan mengatur nilai yang disediakan foo () secara manual) daripada mewarisi kelas baru dari beberapa kelas dasar yang membutuhkan fungsi foo () akan ditentukan.
Mengapa?
Karena pewarisan adalah cara yang buruk dalam memindahkan informasi .
Komposisi memiliki keunggulan nyata di sini: hubungannya dapat dibalik: "kelas induk" atau "pekerja abstrak" dapat mengagregasi objek "anak" tertentu yang mengimplementasikan antarmuka tertentu + setiap anak dapat diatur di dalam jenis induk lain, yang menerima itu tipe . Dan bisa ada sejumlah objek, misalnya MergeSort atau QuickSort dapat mengurutkan daftar objek yang mengimplementasikan abstrak -bandingkan antarmuka. Atau dengan kata lain: grup objek apa pun yang menerapkan "foo ()" dan grup objek lain yang dapat memanfaatkan objek yang memiliki "foo ()" dapat bermain bersama.
Saya dapat memikirkan tiga alasan nyata untuk menggunakan warisan:
Jika ini benar, maka mungkin perlu menggunakan warisan.
Tidak ada yang buruk dalam menggunakan alasan 1, itu hal yang sangat baik untuk memiliki antarmuka yang solid pada objek Anda. Ini dapat dilakukan menggunakan komposisi atau dengan warisan, tidak masalah - jika antarmuka ini sederhana dan tidak berubah. Biasanya warisan cukup efektif di sini.
Jika alasannya adalah nomor 2, itu menjadi sedikit rumit. Apakah Anda benar-benar hanya perlu menggunakan kelas dasar yang sama? Secara umum, hanya menggunakan kelas dasar yang sama tidak cukup baik, tetapi mungkin merupakan persyaratan kerangka kerja Anda, pertimbangan desain yang tidak dapat dihindari.
Namun, jika Anda ingin menggunakan variabel pribadi, case 3, maka Anda mungkin dalam masalah. Jika Anda menganggap variabel global tidak aman, maka Anda harus mempertimbangkan menggunakan pewarisan untuk mendapatkan akses ke variabel pribadi juga tidak aman . Ingat, variabel global tidak semuanya buruk - basis data pada dasarnya adalah kumpulan besar variabel global. Tetapi jika Anda bisa mengatasinya, maka itu cukup baik.
sumber
Untuk menjawab pertanyaan ini dari perspektif yang berbeda untuk programmer yang lebih baru:
Warisan sering diajarkan sejak dini ketika kita mempelajari pemrograman berorientasi objek, jadi itu dipandang sebagai solusi mudah untuk masalah umum.
Kedengarannya hebat, tetapi dalam praktiknya hampir tidak pernah berhasil, karena salah satu dari beberapa alasan:
Pada akhirnya, kami mengikat kode kami dalam beberapa simpul yang sulit dan tidak mendapat manfaat apa pun darinya kecuali bahwa kami dapat berkata, "Keren, saya belajar tentang warisan dan sekarang saya menggunakannya." Itu tidak dimaksudkan untuk merendahkan karena kita semua sudah melakukannya. Tetapi kami semua melakukannya karena tidak ada yang mengatakan kepada kami untuk tidak melakukannya.
Segera setelah seseorang menjelaskan "mendukung komposisi daripada warisan" kepada saya, saya berpikir kembali setiap kali saya mencoba untuk berbagi fungsionalitas antara kelas menggunakan warisan dan menyadari bahwa sebagian besar waktu itu tidak benar-benar berfungsi dengan baik.
Penangkal adalah Prinsip Tanggung Jawab Tunggal . Anggap saja sebagai kendala. Kelas saya harus melakukan satu hal. Saya harus bisa memberi nama kelas saya yang entah bagaimana menggambarkan satu hal yang dilakukannya. (Ada pengecualian untuk semuanya, tetapi aturan absolut kadang-kadang lebih baik ketika kita belajar.) Karena itu saya tidak bisa menulis kelas dasar yang disebut
ObjectBaseThatContainsVariousFunctionsNeededByDifferentClasses
. Apa pun fungsi berbeda yang saya butuhkan harus berada di kelasnya sendiri, dan kemudian kelas lain yang membutuhkan fungsionalitas itu dapat bergantung pada kelas itu, bukan mewarisi darinya.Dengan risiko penyederhanaan yang berlebihan, itu adalah komposisi - menyusun beberapa kelas untuk bekerja bersama. Dan begitu kita membentuk kebiasaan itu, kita mendapati bahwa itu jauh lebih fleksibel, dapat dipertahankan, dan dapat diuji daripada menggunakan warisan.
sumber
Selain memiliki / memiliki pertimbangan, seseorang juga harus mempertimbangkan "kedalaman" warisan yang harus dilalui objek Anda. Apa pun yang melebihi lima atau enam tingkat warisan dapat menyebabkan masalah casting dan tinju / unboxing yang tidak terduga, dan dalam kasus-kasus itu mungkin lebih bijaksana untuk menyusun objek Anda sebagai gantinya.
sumber
Ketika Anda memiliki hubungan is-a antara dua kelas (anjing contoh adalah anjing), Anda pergi untuk warisan.
Di sisi lain ketika Anda memiliki memiliki-a atau hubungan sifat antara dua kelas (siswa memiliki program) atau (studi guru kursus), Anda memilih komposisi.
sumber
Cara sederhana untuk memahami hal ini adalah pewarisan harus digunakan ketika Anda membutuhkan objek kelas Anda untuk memiliki antarmuka yang sama dengan kelas induknya, sehingga warisan tersebut dapat diperlakukan sebagai objek dari kelas induk (upcasting) . Selain itu, pemanggilan fungsi pada objek kelas turunan akan tetap sama di mana-mana dalam kode, tetapi metode spesifik untuk memanggil akan ditentukan saat runtime (yaitu implementasi level rendah berbeda, antarmuka level tinggi tetap sama).
Komposisi harus digunakan ketika Anda tidak perlu kelas baru untuk memiliki antarmuka yang sama, yaitu Anda ingin menyembunyikan aspek-aspek tertentu dari implementasi kelas yang tidak perlu diketahui oleh pengguna kelas itu. Jadi komposisi lebih di jalan mendukung enkapsulasi (yaitu menyembunyikan implementasi) sementara warisan dimaksudkan untuk mendukung abstraksi (yaitu memberikan representasi yang disederhanakan dari sesuatu, dalam hal ini antarmuka yang sama untuk berbagai jenis dengan internal yang berbeda).
sumber
Subtyping sesuai dan lebih kuat di mana invarian dapat dihitung , jika tidak gunakan komposisi fungsi untuk diperpanjang.
sumber
Saya setuju dengan @Pavel, ketika dia berkata, ada tempat untuk komposisi dan ada tempat untuk warisan.
Saya pikir warisan harus digunakan jika jawaban Anda adalah afirmatif untuk semua pertanyaan ini.
Namun, jika niat Anda murni untuk menggunakan kembali kode, maka komposisi yang paling mungkin adalah pilihan desain yang lebih baik.
sumber
Warisan adalah machanism yang sangat kuat untuk penggunaan kembali kode. Namun perlu digunakan dengan benar. Saya akan mengatakan bahwa pewarisan digunakan dengan benar jika subclass juga merupakan subtipe dari kelas induk. Sebagaimana disebutkan di atas, Prinsip Pergantian Liskov adalah poin utama di sini.
Subkelas tidak sama dengan subtipe. Anda dapat membuat subclass yang bukan subtipe (dan ini adalah saat Anda harus menggunakan komposisi). Untuk memahami apa subtipe itu, mari kita mulai memberikan penjelasan tentang apa itu tipe.
Ketika kita mengatakan bahwa angka 5 adalah tipe integer, kita menyatakan bahwa 5 milik satu set nilai yang mungkin (sebagai contoh, lihat nilai yang mungkin untuk tipe primitif Java). Kami juga menyatakan bahwa ada serangkaian metode yang valid yang dapat saya lakukan pada nilai seperti penjumlahan dan pengurangan. Dan akhirnya kami menyatakan bahwa ada satu set properti yang selalu puas, misalnya, jika saya menambahkan nilai 3 dan 5, saya akan mendapatkan 8 sebagai hasilnya.
Untuk memberikan contoh lain, pikirkan tentang tipe data abstrak, Kumpulan bilangan bulat dan Daftar bilangan bulat, nilai yang dapat mereka pegang dibatasi untuk bilangan bulat. Keduanya mendukung serangkaian metode, seperti add (newValue) dan size (). Dan mereka berdua memiliki sifat yang berbeda (kelas invarian), Set tidak memungkinkan duplikat sementara Daftar memang memungkinkan duplikat (tentu saja ada properti lain yang mereka berdua memuaskan).
Subtipe juga merupakan tipe, yang memiliki hubungan dengan tipe lain, disebut tipe induk (atau supertipe). Subtipe harus memenuhi fitur (nilai, metode, dan properti) dari tipe induk. Relasi berarti bahwa dalam konteks apa pun di mana supertipe diharapkan, dapat diganti oleh subtipe, tanpa memengaruhi perilaku eksekusi. Mari kita lihat beberapa kode untuk memberikan contoh apa yang saya katakan. Misalkan saya menulis Daftar bilangan bulat (dalam semacam bahasa semu):
Kemudian, saya menulis Set bilangan bulat sebagai subclass dari Daftar bilangan bulat:
Kelas himpunan bilangan bulat kami adalah subkelas Daftar Bilangan Bulat, tetapi bukan subtipe, karena tidak memenuhi semua fitur dari kelas Daftar. Nilai-nilai, dan tanda tangan metode puas tetapi properti tidak. Perilaku metode add (Integer) telah jelas diubah, tidak mempertahankan properti tipe induk. Pikirkan dari sudut pandang klien kelas Anda. Mereka mungkin menerima Set bilangan bulat di mana Daftar bilangan bulat diharapkan. Klien mungkin ingin menambahkan nilai dan mendapatkan nilai itu ditambahkan ke Daftar bahkan jika nilai itu sudah ada di Daftar. Tapi dia tidak akan mendapatkan perilaku itu jika nilainya ada. Kejutan besar untuknya!
Ini adalah contoh klasik dari penggunaan warisan yang tidak tepat. Gunakan komposisi dalam hal ini.
(sebuah fragmen dari: gunakan warisan dengan benar ).
sumber
Aturan praktis yang saya dengar adalah pewarisan harus digunakan ketika hubungan dan komposisi "is-a" ketika "has-a". Bahkan dengan itu saya merasa bahwa Anda harus selalu bersandar pada komposisi karena menghilangkan banyak kerumitan.
sumber
Komposisi v / s Warisan adalah subjek yang luas. Tidak ada jawaban nyata untuk apa yang lebih baik karena saya pikir itu semua tergantung pada desain sistem.
Umumnya jenis hubungan antar objek memberikan informasi yang lebih baik untuk memilih salah satunya.
Jika tipe relasi adalah relasi "IS-A" maka Inheritance adalah pendekatan yang lebih baik. jika tidak, tipe relasi adalah relasi "HAS-A" maka komposisi akan mendekati dengan lebih baik.
Ini sepenuhnya tergantung pada hubungan entitas.
sumber
Meskipun Komposisi lebih disukai, saya ingin menyoroti kelebihan Warisan dan kontra Komposisi .
Kelebihan Warisan:
Itu membangun hubungan " IS A" yang logis . Jika Mobil dan Truk adalah dua jenis Kendaraan (kelas dasar), kelas anak adalah kelas dasar.
yaitu
Mobil adalah Kendaraan
Truk adalah Kendaraan
Dengan pewarisan, Anda dapat menentukan / memodifikasi / memperluas kemampuan
Kekurangan Komposisi:
mis. Jika Mobil mengandung Kendaraan dan jika Anda harus mendapatkan harga Mobil , yang telah ditentukan dalam Kendaraan , kode Anda akan seperti ini
sumber
Seperti yang dikatakan banyak orang, saya akan mulai dengan cek - apakah ada hubungan "is-a". Jika ada, saya biasanya memeriksa yang berikut:
Misalnya 1. Akuntan adalah Karyawan. Tapi saya tidak akan menggunakan warisan karena objek Karyawan dapat dipakai.
Misal 2. Buku adalah Item Jual. Sellingitem tidak bisa dipakai - itu adalah konsep abstrak. Karenanya saya akan menggunakan inheritacne. SellingItem adalah kelas dasar abstrak (atau antarmuka dalam C #)
Apa pendapat Anda tentang pendekatan ini?
Juga, saya mendukung jawaban @anon di Mengapa menggunakan warisan sama sekali?
@ MatthieuM. mengatakan di /software/12439/code-smell-inheritance-abuse/12448#comment303759_12448
REFERENSI
sumber
Saya melihat tidak ada yang menyebutkan masalah berlian , yang mungkin timbul dengan warisan.
Sekilas, jika kelas B dan C mewarisi A dan keduanya menimpa metode X, dan kelas keempat D, mewarisi dari B dan C, dan tidak menimpa X, implementasi XD mana yang seharusnya digunakan?
Wikipedia menawarkan tinjauan yang bagus tentang topik yang sedang dibahas dalam pertanyaan ini.
sumber