"Lebih suka komposisi daripada warisan" - Apakah satu-satunya alasan untuk bertahan terhadap perubahan tanda tangan?


13

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?




2
Kutipan tidak mengatakan itu adalah alasan "satu-satunya".
Tulains Córdova

3
Komposisi hampir selalu lebih baik, karena warisan umumnya menambah kerumitan, penggandaan, pembacaan kode, dan mengurangi fleksibilitas. Tetapi ada beberapa pengecualian, terkadang satu atau dua kelas dasar tidak sakit. Itu sebabnya ' lebih disukai ' daripada ' selalu '.
Mark Rogers

Jawaban:


27

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.


6
Ada banyak TV dengan pemutar VCR dan DVD yang terpasang di akhir tahun 90-an dan awal 2000-an.
JAB

@JAB dan banyak (kebanyakan?) TV yang dijual hari ini mendukung streaming, dalam hal ini.
Casey

4
@ JAB Dan tidak ada dari mereka yang dapat menjual DVD playernya untuk membantu membeli pemain Bluray
Alexander - Reinstate Monica

1
@ JAB Benar. Pernyataan "Apakah Anda pernah melihat salah satu TV yang memiliki pemutar VCR built-in?" menyiratkan mereka ada.
JimmyJames

4
@Casey Karena jelas, tidak akan pernah ada teknologi lain yang akan menggantikan yang ini, kan? Saya berani bertaruh bahwa salah satu dari TV tersebut juga mendukung input dari perangkat eksternal untuk berjaga-jaga seandainya seseorang ingin menggunakan sesuatu yang lain tanpa membeli TV baru. Atau lebih tepatnya, beberapa pembeli berpendidikan bersedia untuk membeli TV yang tidak memiliki input seperti itu. Premis saya bukanlah bahwa pendekatan ini tidak ada dan saya juga tidak akan mengklaim warisan tidak ada. Intinya adalah bahwa pendekatan semacam itu pada dasarnya kurang fleksibel daripada komposisi.
JimmyJames

4

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.


Saya melihat mentalitas Jawa di sini. OOP adalah tentang data yang dikemas bersama dengan fungsi-fungsi yang bekerja padanya - apa yang Anda gambarkan disebut "modul". Anda benar bahwa menghindari warisan adalah ide yang baik ketika Anda menggunakan kembali obyek sebagai modul, tetapi saya merasa mengganggu bahwa Anda tampaknya merekomendasikan itu sebagai cara yang disukai untuk menggunakan objek.
Brilliand

1
@Brilliand Jika Anda hanya tahu berapa banyak kode java ditulis dalam gaya seperti yang Anda gambarkan dan seberapa mengerikan ini ... Smalltalk memperkenalkan istilah OOP dan dirancang di sekitar objek yang menerima pesan. : "Komputasi harus dilihat sebagai kemampuan intrinsik dari objek yang dapat secara seragam dipanggil dengan mengirim pesan." Tidak disebutkan data dan fungsi yang beroperasi di sana. Maaf, tapi menurut saya Anda salah besar. Jika itu hanya tentang data dan fungsi seseorang dapat dengan senang hati melanjutkan penulisan struct.
marstato

@Tsar sayangnya, meskipun Java mendukung metode statis, budaya Java tidak
k_g

@Brilliand Siapa yang peduli tentang "OOP"? Yang penting adalah bagaimana Anda bisa menggunakannya dan hasil menggunakannya dengan cara itu.
user253751

1
Setelah berdiskusi dengan @Tsar: kelas Java tidak harus dipakai, dan memang, kelas seperti UserRegistrationService dan EmailService biasanya tidak boleh di-instantiated - kelas-kelas tanpa data terkait bekerja paling baik sebagai kelas statis (dan karenanya, tidak relevan dengan yang asli) pertanyaan). Saya akan menghapus beberapa komentar saya sebelumnya, tetapi kritik asli saya atas jawaban marsato bertahan.
Brilliand

4

"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 Vehiclekelas, maka kita berasal Car, Airplane, Balloondan Ship.

Catatan : Jika Anda perlu membuat contoh ini, berpura-puralah ini adalah jenis objek dalam gim video.

Kemudian Cardan Airplanemungkin 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 Airplanedan 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 FlyingVehiculekelas. Mereka akan mengatakan bahwa itu AirplaneadalahFlyingVehicule (warisan kelas), tetapi kita dapat mengatakan bahwa Airplanememiliki Flyingkomponen (komposisi) dan Airplanemerupakan 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 Amewarisi atau menulisB ?

Jika AApakah spesialisasi Byang harus memenuhi prinsip substitusi Liskov , pewarisan itu layak, bahkan diinginkan. Jika ada situasi di mana Abukan substitusi yang valid Bmaka kita tidak boleh menggunakan warisan.

Kita mungkin tertarik pada komposisi sebagai bentuk pemrograman defensif, untuk mempertahankan kelas turunan . Secara khusus, begitu Anda mulai menggunakan Buntuk tujuan lain yang berbeda, mungkin ada tekanan untuk mengubah atau memperluasnya agar lebih cocok untuk tujuan tersebut. Jika ada risiko yang Bdapat mengekspos metode yang dapat mengakibatkan kondisi yang tidak valid di Akita harus menggunakan komposisi alih-alih warisan. Bahkan jika kita adalah penulis keduanya Bdan 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 Byang Atidak 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 Ctidak akan mewarisi dari kelas B. Sebaliknya kelas Anda Cakan 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.


Uh ... .Net framework menggunakan beberapa jenis aliran yang diwarisi untuk melakukan pekerjaan yang terkait dengan aliran itu. Ini bekerja sangat baik dan sangat mudah digunakan dan diperluas. Teladan Anda tidak terlalu bagus ...
T. Sar

1
@ CT "sangat super mudah digunakan dan diperluas" Apakah Anda pernah mencoba untuk tee stream standar keluar ke Debug? Itulah satu-satunya pengalaman saya mencoba untuk memperluas aliran mereka. Itu tidak mudah. Itu adalah mimpi buruk yang berakhir pada saya mengesampingkan setiap metode di kelas secara eksplisit. Sangat berulang. Dan jika Anda melihat kode sumber .NET, mengesampingkan semuanya secara eksplisit adalah cara mereka melakukannya. Jadi saya tidak yakin itu mudah untuk diperpanjang.
jpmc26

Membaca dan menulis struct dari aliran lebih baik dilakukan dengan fungsi bebas, atau metode multimetode.
user253751

@ jpmc26 Maaf pengalaman Anda tidak terlalu baik. Saya tidak memiliki masalah sendiri menggunakan atau menerapkan hal-hal yang berhubungan dengan aliran untuk hal-hal yang berbeda.
T. Sar

3

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.


Jadi bagian ini memberikan contoh Komposisi, kan? Saya bertanya karena tidak jelas dalam artikel itu.
Utku

1
Ya, "Komposisi atas warisan" tidak berarti Anda tidak memiliki antarmuka, jika Anda melihat objek Player Anda dapat melihat bahwa itu cocok dengan baik dalam hierarki, tetapi pada kode (maafkan kebrutalan) tidak berasal dari warisan, tetapi dari komposisi dengan beberapa kelas yang melakukan pekerjaan
lbenini

2

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.


0

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.


Atau Anda membuat kelas Karyawan dengan pointer ke Daftar Peran, dan setiap pekerjaan aktual memperluas Peran. Teladan Anda dapat dibuat untuk menggunakan warisan dengan cara yang sangat baik dan masuk akal tanpa terlalu banyak bekerja.
T. Sar

0

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.


0

Apakah itu benar-benar satu-satunya alasan untuk memilih komposisi daripada warisan?

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 JFramekelas mewakili sebuah jendela. Untuk membuat jendela, Anda membuat instance JFramedan kemudian memanggil fungsi pada instance itu untuk menambahkan barang ke sana dan menunjukkannya.

Banyak tutorial (bahkan yang resmi) merekomendasikan agar Anda memperluas JFramesehingga Anda dapat memperlakukan instance kelas Anda sebagai instance JFrame. Tapi imho (dan pendapat banyak orang lain) adalah bahwa ini adalah kebiasaan yang sangat buruk untuk masuk! Sebagai gantinya, Anda harus membuat instance JFramedan memanggil fungsi pada instance itu. Sama sekali tidak perlu diperluas JFramedalam hal ini, karena Anda tidak mengubah perilaku default dari kelas induk.

Di sisi lain, salah satu cara untuk melakukan lukisan kustom adalah dengan memperluas JPanelkelas dan menimpa paintComponent()fungsinya. Ini adalah penggunaan warisan yang bagus. Jika saya melihat-lihat kode Anda dan saya melihat bahwa Anda telah memperluas JPaneldan mengganti paintComponent()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.


Menambahkan seluruh kelas pabrik dengan metode tunggal yang mengembalikan instance objek sehingga Anda dapat menggunakan komposisi alih-alih pewarisan tidak benar-benar membuat kode Anda lebih mudah dibaca ... (Ya, Anda memerlukan ini jika Anda memerlukan instance baru setiap kali metode tertentu disebut.) Itu tidak berarti warisan itu baik, tetapi komposisi tidak selalu meningkatkan keterbacaan.
jpmc26

1
@ jpmc26 ... mengapa Anda membutuhkan kelas pabrik hanya untuk membuat instance baru dari kelas? Dan bagaimana pewarisan meningkatkan keterbacaan dari apa yang Anda gambarkan?
Kevin Workman

Apakah saya tidak secara eksplisit memberi Anda alasan untuk pabrik seperti itu di komentar di atas? Ini lebih mudah dibaca karena menghasilkan lebih sedikit kode untuk dibaca . Anda dapat mencapai perilaku yang sama dengan metode abstrak tunggal di kelas dasar dan beberapa subclass. (Lagi pula, Anda akan memiliki implementasi yang berbeda dari kelas yang dikomposisikan untuk masing-masing.) Jika detail membangun objek sangat terkait dengan kelas dasar, itu tidak akan menemukan banyak digunakan di tempat lain dalam kode Anda, mengurangi lebih lanjut manfaat membuat kelas yang terpisah. Kemudian ia menyimpan bagian-bagian kode yang terkait erat satu sama lain.
jpmc26

Seperti yang saya katakan, ini tidak selalu berarti pewarisan itu baik, tetapi itu menunjukkan bagaimana komposisi tidak meningkatkan keterbacaan dalam beberapa situasi.
jpmc26

1
Tanpa melihat contoh spesifik, sulit untuk benar-benar berkomentar. Tapi apa yang Anda gambarkan terdengar seperti kasus over-engineering bagi saya. Perlu instance kelas? Kata newkunci memberi Anda satu. Pokoknya terimakasih atas pembahasannya.
Kevin Workman
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.