"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 berasal Car
, Airplane
, Balloon
dan Ship
.
Catatan : Jika Anda perlu membuat contoh ini, berpura-puralah ini adalah jenis objek dalam gim video.
Kemudian Car
dan Airplane
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 antara Airplane
dan Balloon
. 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 itu Airplane
adalahFlyingVehicule
(warisan kelas), tetapi kita dapat mengatakan bahwa Airplane
memiliki Flying
komponen (komposisi) dan Airplane
merupakan IFlyingVehicule
(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 spesialisasi B
yang harus memenuhi prinsip substitusi Liskov , pewarisan itu layak, bahkan diinginkan. Jika ada situasi di mana A
bukan substitusi yang valid B
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 yang B
dapat mengekspos metode yang dapat mengakibatkan kondisi yang tidak valid di A
kita harus menggunakan komposisi alih-alih warisan. Bahkan jika kita adalah penulis keduanya B
dan A
, itu adalah satu hal yang kurang perlu dikhawatirkan, oleh karena itu komposisi memudahkan penggunaan kembali B
.
Kami bahkan dapat berdebat bahwa jika ada fitur di dalamnya B
yang A
tidak perlu (dan kami tidak tahu apakah fitur-fitur itu dapat mengakibatkan kondisi yang tidak valid A
, 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:
Warisan memperlihatkan subclass ke detail implementasi orang tuanya, sering dikatakan bahwa 'pewarisan memecah enkapsulasi'
- Pola Desain: Elemen Perangkat Lunak Berorientasi Objek Dapat Digunakan Kembali, Geng Empat
Ya, itu kelas induk yang didesain dengan buruk. Itu sebabnya Anda harus:
Desain untuk warisan, atau melarangnya.
- Java Efektif, Josh Bloch
Masalah Yo-Yo
Kasus lain di mana komposisi membantu adalah masalah Yo-Yo . Ini adalah kutipan dari Wikipedia:
Dalam pengembangan perangkat lunak, masalah yo-yo adalah anti-pola yang terjadi ketika seorang programmer harus membaca dan memahami sebuah program yang grafik pewarisannya begitu panjang dan rumit sehingga programmer harus terus membalik-balik berbagai definisi kelas untuk mengikuti aliran kontrol program.
Anda dapat menyelesaikan, misalnya: Kelas Anda C
tidak akan mewarisi dari kelas B
. Sebaliknya kelas Anda C
akan memiliki anggota tipe A
, yang mungkin atau mungkin bukan (atau memiliki) objek tipe B
. Dengan cara ini Anda tidak akan memprogram terhadap detail implementasi B
, tetapi terhadap kontrak yang ditawarkan antarmuka A
.
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.