Halaman ini menganjurkan komposisi atas warisan dengan argumen berikut (diucapkan ulang dengan kata-kata saya):
Perubahan tanda tangan dari metode superclass (yang belum ditimpa dalam subclass) menyebabkan perubahan tambahan di banyak tempat ketika kita menggunakan Inheritance. Namun, ketika kami menggunakan Komposisi, perubahan tambahan yang diperlukan hanya di satu tempat: Subkelas.
Apakah itu benar-benar satu-satunya alasan untuk memilih komposisi daripada warisan? Karena jika itu masalahnya, masalah ini dapat dengan mudah diatasi dengan menerapkan gaya pengkodean yang menganjurkan mengganti semua metode dari superclass, bahkan jika subkelas tidak mengubah implementasi (yaitu, menempatkan penggantian tiruan dalam subkelas). Apakah saya melewatkan sesuatu di sini?
Jawaban:
Saya suka analoginya jadi ini salah satunya: Pernahkah Anda melihat salah satu TV yang memiliki pemutar VCR? Bagaimana dengan VCR dan pemutar DVD? Atau yang memiliki Blu-ray, DVD dan VCR. Oh dan sekarang semua orang streaming jadi kami perlu merancang perangkat TV baru ...
Kemungkinan besar, jika Anda memiliki TV, Anda tidak memilikinya seperti di atas. Mungkin sebagian besar dari mereka tidak pernah ada. Kemungkinan besar Anda memiliki monitor atau set yang memiliki banyak input sehingga Anda tidak perlu set baru setiap kali ada jenis perangkat input baru.
Warisan seperti TV dengan VCR built-in. Jika Anda memiliki 3 tipe perilaku yang ingin Anda gunakan bersama dan masing-masing memiliki 2 opsi untuk implementasi, Anda perlu 8 kelas yang berbeda untuk mewakili semua itu. Ketika Anda menambahkan lebih banyak opsi, jumlahnya akan meledak. Jika Anda menggunakan komposisi sebagai gantinya, Anda menghindari masalah kombinatorial dan desain akan cenderung lebih dapat diperluas.
sumber
Tidak, bukan itu. Dalam pengalaman saya, komposisi atas warisan adalah hasil dari berpikir tentang perangkat lunak Anda sebagai sekelompok objek dengan perilaku yang berbicara satu sama lain / objek menyediakan layanan satu sama lain, bukan kelas dan data .
Dengan cara ini, Anda memiliki beberapa objek yang menyediakan serviec. Anda menggunakannya sebagai blok bangunan (sangat mirip Lego) untuk mengaktifkan perilaku lain (mis. UserRegistrationService menggunakan layanan yang disediakan oleh EmailerService dan UserRepository ).
Ketika membangun sistem yang lebih besar dengan pendekatan ini, saya biasanya menggunakan warisan dalam beberapa kasus. Pada akhirnya warisan adalah alat yang sangat kuat yang harus digunakan dengan hati-hati dan hanya jika sesuai.
sumber
"Lebih suka komposisi daripada warisan" hanyalah heuristik yang bagus
Anda harus mempertimbangkan konteksnya, karena ini bukan aturan universal. Jangan menganggap itu berarti tidak pernah menggunakan warisan ketika Anda dapat melakukan komposisi. Jika itu masalahnya, Anda akan memperbaikinya dengan melarang pewarisan.
Saya berharap poin ini akan jelas di pos ini.
Saya tidak akan mencoba mempertahankan keunggulan komposisi dengan sendirinya. Yang saya anggap di luar topik. Sebagai gantinya, saya akan berbicara tentang beberapa situasi ketika pengembang dapat mempertimbangkan pewarisan yang lebih baik menggunakan komposisi. Pada catatan itu, warisan memiliki kelebihannya sendiri, yang saya juga pertimbangkan di luar topik.
Contoh kendaraan
Saya menulis tentang pengembang yang mencoba melakukan sesuatu dengan cara bodoh, untuk tujuan naratif
Mari kita pergi untuk varian contoh klasik yang beberapa program OOP menggunakan ... Kami memiliki
Vehicle
kelas, maka kita berasalCar
,Airplane
,Balloon
danShip
.Catatan : Jika Anda perlu membuat contoh ini, berpura-puralah ini adalah jenis objek dalam gim video.
Kemudian
Car
danAirplane
mungkin memiliki beberapa kode umum, karena keduanya dapat bergulir di atas roda di darat. Pengembang dapat mempertimbangkan untuk membuat kelas perantara dalam rantai pewarisan untuk itu. Namun, sebenarnya ada juga beberapa kode bersama antaraAirplane
danBalloon
. Mereka dapat mempertimbangkan untuk membuat kelas perantara lain dalam rantai pewarisan untuk itu.Dengan demikian, pengembang akan mencari banyak warisan. Pada titik di mana pengembang mencari pewarisan berganda, desainnya salah.
Lebih baik memodelkan perilaku ini sebagai antarmuka dan komposisi, sehingga kita dapat menggunakannya kembali tanpa harus mengalami pewarisan banyak kelas. Jika pengembang, misalnya, buat
FlyingVehicule
kelas. Mereka akan mengatakan bahwa ituAirplane
adalahFlyingVehicule
(warisan kelas), tetapi kita dapat mengatakan bahwaAirplane
memilikiFlying
komponen (komposisi) danAirplane
merupakanIFlyingVehicule
(warisan antarmuka).Menggunakan antarmuka, jika perlu, kita dapat memiliki banyak pewarisan (antarmuka). Selain itu, Anda tidak menyertai implementasi tertentu. Meningkatkan reusability dan testability dari kode Anda.
Ingatlah bahwa pewarisan adalah alat untuk polimorfisme. Selain itu, polimorfisme adalah alat untuk dapat digunakan kembali. Jika Anda dapat meningkatkan penggunaan kembali kode Anda dengan menggunakan komposisi, maka lakukanlah. Jika Anda tidak yakin komposisi apa pun atau tidak memberikan reusability yang lebih baik, "Lebih suka komposisi daripada warisan" adalah heuristik yang baik.
Semua itu tanpa menyebutkan
Amphibious
.Faktanya, kita mungkin tidak membutuhkan hal-hal yang gagal. Stephen Hurn memiliki contoh yang lebih fasih dalam artikelnya "Komposisi Favour Over Inheritance" bagian 1 dan bagian 2 .
Substitutabilitas dan Enkapsulasi
Harus
A
mewarisi atau menulisB
?Jika
A
Apakah spesialisasiB
yang harus memenuhi prinsip substitusi Liskov , pewarisan itu layak, bahkan diinginkan. Jika ada situasi di manaA
bukan substitusi yang validB
maka kita tidak boleh menggunakan warisan.Kita mungkin tertarik pada komposisi sebagai bentuk pemrograman defensif, untuk mempertahankan kelas turunan . Secara khusus, begitu Anda mulai menggunakan
B
untuk tujuan lain yang berbeda, mungkin ada tekanan untuk mengubah atau memperluasnya agar lebih cocok untuk tujuan tersebut. Jika ada risiko yangB
dapat mengekspos metode yang dapat mengakibatkan kondisi yang tidak valid diA
kita harus menggunakan komposisi alih-alih warisan. Bahkan jika kita adalah penulis keduanyaB
danA
, itu adalah satu hal yang kurang perlu dikhawatirkan, oleh karena itu komposisi memudahkan penggunaan kembaliB
.Kami bahkan dapat berdebat bahwa jika ada fitur di dalamnya
B
yangA
tidak perlu (dan kami tidak tahu apakah fitur-fitur itu dapat mengakibatkan kondisi yang tidak validA
, baik dalam implementasi saat ini atau di masa depan), adalah ide yang baik untuk menggunakan komposisi bukannya warisan.Komposisi juga memiliki keunggulan memungkinkan implementasi switching, dan mengurangi ejekan.
Catatan : ada beberapa situasi di mana kami ingin menggunakan komposisi meskipun substitusi sedang berlaku. Kami mengarsipkan substitusi dengan menggunakan antarmuka atau kelas abstrak (yang mana digunakan saat topik lain) dan kemudian menggunakan komposisi dengan injeksi ketergantungan dari implementasi nyata.
Akhirnya, tentu saja, ada argumen bahwa kita harus menggunakan komposisi untuk mempertahankan kelas induk karena warisan merusak enkapsulasi kelas induk:
- Pola Desain: Elemen Perangkat Lunak Berorientasi Objek Dapat Digunakan Kembali, Geng Empat
Ya, itu kelas induk yang didesain dengan buruk. Itu sebabnya Anda harus:
- Java Efektif, Josh Bloch
Masalah Yo-Yo
Kasus lain di mana komposisi membantu adalah masalah Yo-Yo . Ini adalah kutipan dari Wikipedia:
Anda dapat menyelesaikan, misalnya: Kelas Anda
C
tidak akan mewarisi dari kelasB
. Sebaliknya kelas AndaC
akan memiliki anggota tipeA
, yang mungkin atau mungkin bukan (atau memiliki) objek tipeB
. Dengan cara ini Anda tidak akan memprogram terhadap detail implementasiB
, tetapi terhadap kontrak yang ditawarkan antarmukaA
.Contoh balasan
Banyak kerangka kerja yang lebih memilih pewarisan daripada komposisi (yang merupakan kebalikan dari apa yang telah kami pertanyakan). Pengembang dapat melakukan ini karena mereka menempatkan banyak pekerjaan ke dalam kelas dasar mereka yang diimplementasikan dengan komposisi akan meningkatkan ukuran kode klien. Terkadang ini karena keterbatasan bahasa.
Sebagai contoh, kerangka kerja ORM PHP dapat membuat kelas dasar yang menggunakan metode ajaib untuk memungkinkan penulisan kode seolah-olah objek memiliki properti nyata. Alih-alih kode yang ditangani oleh metode ajaib akan menuju ke database, kueri untuk bidang yang diminta tertentu (mungkin menyimpannya untuk permintaan di masa mendatang) dan mengembalikannya. Melakukannya dengan komposisi akan mengharuskan klien untuk membuat properti untuk setiap bidang atau menulis beberapa versi kode metode ajaib.
Tambahan : Ada beberapa cara lain yang memungkinkan perluasan objek ORM. Jadi, saya tidak berpikir warisan diperlukan pada kasus ini. Itu lebih murah.
Untuk contoh lain, mesin video game dapat membuat kelas dasar yang menggunakan kode asli tergantung pada platform target untuk melakukan rendering 3D dan penanganan acara. Kode ini rumit dan spesifik untuk platform. Akan mahal dan rawan kesalahan bagi pengguna pengembang mesin untuk berurusan dengan kode ini, pada kenyataannya, itu adalah bagian dari alasan menggunakan mesin.
Selain itu, tanpa bagian rendering 3D, ini adalah berapa banyak kerangka kerja widget bekerja. Ini membebaskan Anda dari kekhawatiran untuk menangani pesan OS ... pada kenyataannya, dalam banyak bahasa Anda tidak dapat menulis kode seperti itu tanpa suatu bentuk penawaran asli. Selain itu, jika Anda melakukannya, itu akan memperketat portabilitas Anda. Sebaliknya, dengan warisan, asalkan pengembang tidak merusak kompatibilitas (terlalu banyak); Anda akan dapat dengan mudah port kode Anda ke platform baru yang mereka dukung di masa depan.
Selain itu, pertimbangkan bahwa berkali-kali kami hanya ingin mengganti beberapa metode dan meninggalkan yang lainnya dengan implementasi default. Seandainya kita menggunakan komposisi, kita harus membuat semua metode itu, bahkan jika hanya mendelegasikan ke objek yang dibungkus.
Dengan argumen ini, ada titik di mana komposisi bisa lebih buruk untuk pemeliharaan daripada pewarisan (ketika kelas dasar terlalu kompleks). Namun, ingatlah bahwa pemeliharaan warisan bisa lebih buruk daripada komposisi (ketika pohon warisan terlalu kompleks), itulah yang saya sebut dalam masalah yo-yo.
Dalam contoh yang disajikan, pengembang jarang berniat untuk menggunakan kembali kode yang dihasilkan melalui warisan di proyek lain. Itu mengurangi reusabilitas penggunaan warisan bukan komposisi. Selain itu, dengan menggunakan warisan kerangka pengembang dapat memberikan banyak kode yang mudah digunakan dan mudah ditemukan.
Pikiran terakhir
Seperti yang Anda lihat, komposisi memiliki beberapa keunggulan dibandingkan pewarisan dalam beberapa situasi, tidak selalu. Penting untuk mempertimbangkan konteks dan berbagai faktor yang terlibat (seperti usabilitas ulang, rawatan, pengujian, dll ...) untuk membuat keputusan. Kembali ke titik pertama: "Suka komposisi atas warisan" adalah hanya heuristik yang baik.
Anda mungkin juga memiliki pemberitahuan bahwa banyak dari situasi yang saya jelaskan dapat diatasi dengan Ciri atau Mixin pada tingkat tertentu. Sayangnya, ini bukan fitur umum dalam daftar besar bahasa, dan mereka biasanya datang dengan biaya kinerja. Untungnya, Metode Perpanjangan sepupu populer mereka dan Implementasi Default mengurangi beberapa situasi.
Saya punya posting baru-baru ini di mana saya berbicara tentang beberapa keuntungan dari antarmuka mengapa kita memerlukan antarmuka antara UI, Bisnis dan akses data di C # . Ini membantu decoupling dan memudahkan reusability dan testability, Anda mungkin tertarik.
sumber
Biasanya ide pendorong Composition-Over-Inheritance adalah untuk memberikan fleksibilitas desain yang lebih besar dan tidak mengurangi jumlah kode yang perlu Anda ubah untuk menyebarkan perubahan (yang menurut saya dipertanyakan).
Contoh di wikipedia memberikan contoh yang lebih baik tentang kekuatan pendekatannya:
Menentukan antarmuka dasar untuk setiap "komposisi-potongan" Anda dapat dengan mudah membuat berbagai "objek-tersusun" dengan variasi yang mungkin sulit dijangkau hanya dengan warisan.
sumber
Garis: "Lebih suka komposisi daripada warisan" hanyalah dorongan ke arah yang benar. Itu tidak akan menyelamatkan Anda dari kebodohan Anda sendiri dan benar-benar memberi Anda kesempatan untuk memperburuk keadaan. Itu karena ada banyak kekuatan di sini.
Alasan utama ini bahkan perlu dikatakan adalah karena programmer malas. Begitu malas mereka akan mengambil solusi apa pun yang berarti mengetik keyboard lebih sedikit. Itulah titik penjualan utama warisan. Saya kurang mengetik hari ini dan beberapa orang lain akan dikacaukan besok. Jadi apa, aku ingin pulang.
Hari berikutnya bos bersikeras saya menggunakan komposisi. Tidak menjelaskan mengapa tetapi bersikeras. Jadi saya mengambil contoh dari apa yang saya warisi dari, mengekspos antarmuka yang identik dengan apa yang saya warisi dari, dan mengimplementasikan antarmuka itu dengan mendelegasikan semua pekerjaan untuk hal yang saya warisi dari. Itu semua mengetik mati otak yang bisa dilakukan dengan alat refactoring dan tampaknya tidak ada gunanya bagi saya tetapi bos menginginkannya jadi saya melakukannya.
Hari berikutnya saya bertanya apakah ini yang diinginkan. Menurut Anda apa kata bos?
Kecuali ada beberapa kebutuhan untuk dapat secara dinamis (pada saat run time) mengubah dari apa yang diwarisi dari (lihat pola negara) ini adalah buang-buang waktu. Bagaimana bos bisa mengatakan itu dan masih mendukung komposisi daripada warisan?
Selain itu tidak melakukan apa pun untuk mencegah kerusakan karena perubahan metode tanda tangan ini sepenuhnya kehilangan keuntungan terbesar komposisi. Tidak seperti warisan, Anda bebas membuat antarmuka yang berbeda . Satu lagi yang sesuai untuk menggunakannya pada level ini. Anda tahu, abstraksi!
Ada beberapa keuntungan kecil untuk secara otomatis mengganti warisan dengan komposisi dan pendelegasian (dan beberapa kelemahan) tetapi menjaga otak Anda selama proses ini kehilangan kesempatan besar.
Tipuan sangat kuat. Gunakan dengan bijak.
sumber
Berikut adalah alasan lain: dalam banyak bahasa OO (Saya memikirkan yang seperti C ++, C # atau Java di sini), tidak ada cara untuk mengubah kelas objek setelah Anda membuatnya.
Misalkan kita sedang membuat database karyawan. Kami memiliki basis karyawan abstrak, dan mulai mendapatkan kelas baru untuk peran tertentu - Insinyur, Satpam, Pengemudi Truk dan sebagainya. Setiap kelas memiliki perilaku khusus untuk peran itu. Semuanya baik.
Kemudian suatu hari seorang Insinyur dipromosikan menjadi Manajer Teknik. Anda tidak dapat mengklasifikasikan ulang Insinyur yang ada. Sebagai gantinya, Anda harus menulis fungsi yang membuat Manajer Teknik, menyalin semua data dari Engineer, menghapus Engineer dari database, lalu menambahkan Engineering Manager yang baru. Dan Anda harus menulis fungsi seperti itu untuk setiap perubahan peran yang mungkin.
Lebih buruk lagi, misalkan Pengemudi Truk pergi cuti sakit jangka panjang, dan seorang Satpam menawarkan untuk melakukan paruh waktu Mengemudi Truk mengisi. Anda sekarang harus menciptakan kelas Pengawal Dan Pengemudi Truk baru hanya untuk mereka.
Itu membuat hidup jadi lebih mudah untuk menciptakan kelas Karyawan yang konkret, dan memperlakukannya sebagai wadah tempat Anda dapat menambahkan properti, seperti peran pekerjaan.
sumber
Warisan menyediakan dua hal:
1) Code-reuse: Dapat dicapai dengan mudah melalui komposisi. Dan faktanya, lebih baik dicapai melalui komposisi karena lebih baik menjaga enkapsulasi.
2) Polimorfisme: Dapat dicapai melalui "antarmuka" (kelas di mana semua metode adalah virtual-virtual).
Argumen yang kuat untuk menggunakan pewarisan non-antarmuka, adalah ketika jumlah metode yang diperlukan besar dan subkelas Anda hanya ingin mengubah sebagian kecil dari mereka. Namun, secara umum antarmuka Anda harus memiliki jejak kaki yang sangat kecil jika Anda berlangganan prinsip "tanggung jawab tunggal" (Anda harus), sehingga kasus ini harus jarang terjadi.
Memiliki gaya pengkodean yang mengharuskan mengganti semua metode kelas induk tidak menghindari menulis sejumlah besar metode pass-through, jadi mengapa tidak menggunakan kembali kode melalui komposisi dan mendapatkan manfaat tambahan dari enkapsulasi? Plus, jika super-class akan diperluas dengan menambahkan metode lain, gaya pengkodean akan membutuhkan penambahan metode itu ke semua sub-kelas (dan ada kemungkinan besar kita kemudian melanggar "segregasi antarmuka" ). Masalah ini tumbuh dengan cepat ketika Anda mulai memiliki beberapa level dalam warisan.
Akhirnya, dalam banyak bahasa, unit menguji objek berbasis "komposisi" jauh lebih mudah daripada unit menguji objek berbasis "pewarisan" dengan mengganti objek anggota dengan objek tiruan / uji / boneka.
sumber
Nggak.
Ada banyak alasan untuk memilih komposisi daripada warisan. Tidak ada jawaban yang benar. Tapi saya akan melemparkan alasan lain untuk mendukung komposisi daripada warisan ke dalam campuran:
Komposisi yang disukai daripada warisan meningkatkan keterbacaan kode Anda , yang sangat penting ketika mengerjakan proyek besar dengan banyak orang.
Begini cara saya berpikir tentang hal itu: ketika saya membaca kode orang lain, jika saya melihat mereka telah menambah kelas, saya akan bertanya perilaku apa di kelas super yang mereka ubah?
Dan kemudian saya akan pergi melalui kode mereka, mencari fungsi yang ditimpa. Jika saya tidak melihat satupun, maka saya mengklasifikasikannya sebagai penyalahgunaan warisan. Mereka seharusnya menggunakan komposisi sebagai gantinya, jika hanya untuk menyelamatkan saya (dan setiap orang lain dalam tim) dari 5 menit membaca kode mereka!
Berikut ini adalah contoh nyata: di Jawa
JFrame
kelas mewakili sebuah jendela. Untuk membuat jendela, Anda membuat instanceJFrame
dan kemudian memanggil fungsi pada instance itu untuk menambahkan barang ke sana dan menunjukkannya.Banyak tutorial (bahkan yang resmi) merekomendasikan agar Anda memperluas
JFrame
sehingga Anda dapat memperlakukan instance kelas Anda sebagai instanceJFrame
. Tapi imho (dan pendapat banyak orang lain) adalah bahwa ini adalah kebiasaan yang sangat buruk untuk masuk! Sebagai gantinya, Anda harus membuat instanceJFrame
dan memanggil fungsi pada instance itu. Sama sekali tidak perlu diperluasJFrame
dalam hal ini, karena Anda tidak mengubah perilaku default dari kelas induk.Di sisi lain, salah satu cara untuk melakukan lukisan kustom adalah dengan memperluas
JPanel
kelas dan menimpapaintComponent()
fungsinya. Ini adalah penggunaan warisan yang bagus. Jika saya melihat-lihat kode Anda dan saya melihat bahwa Anda telah memperluasJPanel
dan menggantipaintComponent()
fungsinya, saya akan tahu persis perilaku apa yang Anda ubah.Jika hanya Anda yang menulis kode sendiri, maka mungkin akan lebih mudah menggunakan warisan seperti halnya menggunakan komposisi. Tetapi berpura-puralah Anda berada dalam lingkungan kelompok dan orang lain akan membaca kode Anda. Menyukai komposisi dibandingkan pewarisan membuat kode Anda lebih mudah dibaca.
sumber
new
kunci memberi Anda satu. Pokoknya terimakasih atas pembahasannya.