Dalam beberapa bulan terakhir, mantra "komposisi kesukaan daripada warisan" tampaknya telah muncul entah dari mana dan menjadi semacam meme dalam komunitas pemrograman. Dan setiap kali saya melihatnya, saya sedikit bingung. Itu seperti seseorang berkata, "Latihan budi daripada palu." Dalam pengalaman saya, komposisi dan warisan adalah dua alat yang berbeda dengan kasus penggunaan yang berbeda, dan memperlakukan mereka seolah-olah mereka dipertukarkan dan satu secara inheren lebih unggul dari yang lain tidak masuk akal.
Juga, saya tidak pernah melihat penjelasan nyata mengapa pewarisan itu buruk dan komposisinya bagus, yang hanya membuat saya lebih curiga. Apakah itu seharusnya diterima dengan iman? Substitusi dan polimorfisme Liskov memiliki manfaat yang terkenal dan jelas, dan IMO mencakup seluruh titik penggunaan pemrograman berorientasi objek, dan tidak ada yang pernah menjelaskan mengapa mereka harus dibuang demi komposisi.
Adakah yang tahu dari mana konsep ini berasal, dan apa alasan di baliknya?
sumber
Jawaban:
Meskipun saya pikir saya sudah mendengar diskusi komposisi-vs-warisan jauh sebelum GoF, saya tidak bisa meletakkan jari saya pada sumber tertentu. Lagipula mungkin Booch.
<rant>
Ah tapi seperti banyak mantra, yang ini telah merosot di sepanjang garis khas:
Meme itu, awalnya dimaksudkan untuk mengarahkan n00bs ke pencerahan, sekarang digunakan sebagai klub untuk memukul mereka secara tidak sadar.
Komposisi dan pewarisan adalah hal-hal yang sangat berbeda, dan tidak boleh dikacaukan satu sama lain. Meskipun benar bahwa komposisi dapat digunakan untuk mensimulasikan pewarisan dengan banyak pekerjaan tambahan , ini tidak menjadikan warisan sebagai warga negara kelas dua, juga tidak menjadikan komposisi sebagai putra favorit. Fakta bahwa banyak n00bs mencoba menggunakan pewarisan sebagai jalan pintas tidak membatalkan mekanisme, dan hampir semua n00b belajar dari kesalahan dan dengan demikian meningkat.
Silakan BERPIKIR tentang desain Anda, dan berhenti semburan semboyan.
</rant>
sumber
Pengalaman.
Seperti yang Anda katakan mereka adalah alat untuk pekerjaan yang berbeda, tetapi frasa muncul karena orang tidak menggunakannya dengan cara itu.
Warisan terutama merupakan alat polimorfik, tetapi beberapa orang, banyak yang akan menanggung risiko kemudian, mencoba menggunakannya sebagai cara menggunakan kembali / berbagi kode. Alasannya adalah "baik jika saya mewarisi maka saya mendapatkan semua metode gratis", tetapi mengabaikan fakta bahwa dua kelas ini berpotensi tidak memiliki hubungan polimorfik.
Jadi mengapa lebih menyukai komposisi daripada pewarisan - yah hanya karena lebih sering hubungan antar kelas bukanlah hubungan polimorfik. Itu ada hanya untuk membantu mengingatkan orang untuk tidak menanggapi sentakan dengan mewarisi.
sumber
Ini bukan ide baru, saya percaya itu sebenarnya diperkenalkan dalam buku pola desain GoF, yang diterbitkan pada tahun 1994.
Masalah utama dengan warisan adalah bahwa itu kotak putih. Menurut definisi, Anda perlu mengetahui detail implementasi dari kelas yang Anda warisi. Dengan komposisi, di sisi lain, Anda hanya peduli dengan antarmuka publik dari kelas yang Anda buat.
Dari buku GoF:
Artikel wikipedia di buku GoF memiliki pengantar yang layak.
sumber
Untuk menjawab bagian dari pertanyaan Anda, saya percaya konsep ini pertama kali muncul dalam Pola Desain GOF : Elemen buku Software Berorientasi Objek Reusable , yang pertama kali diterbitkan pada tahun 1994. Ungkapan tersebut muncul di bagian atas halaman 20, dalam pengantar:
Mereka mengawali pernyataan ini dengan perbandingan singkat tentang warisan dengan komposisi. Mereka tidak mengatakan "tidak pernah menggunakan warisan".
sumber
"Komposisi atas warisan" adalah cara pendek (dan tampaknya menyesatkan) untuk mengatakan "Ketika merasa bahwa data (atau perilaku) suatu kelas harus dimasukkan ke dalam kelas lain, selalu pertimbangkan untuk menggunakan komposisi sebelum menerapkan warisan secara membabi buta".
Mengapa ini benar? Karena warisan menciptakan kopling waktu kompilasi yang ketat antara 2 kelas. Komposisi dalam kontras adalah kopling longgar, yang antara lain memungkinkan pemisahan keprihatinan yang jelas, kemungkinan beralih ketergantungan pada saat runtime dan lebih mudah, lebih mudah diuji ketergantungan ketergantungan.
Itu hanya berarti warisan harus ditangani dengan hati-hati karena harus dibayar mahal, bukan karena tidak berguna. Sebenarnya, "Komposisi atas warisan" sering berakhir dengan "Komposisi + pewarisan atas warisan" karena Anda sering ingin ketergantungan yang Anda buat menjadi superclass abstrak daripada subkelas beton itu sendiri. Ini memungkinkan Anda untuk beralih di antara implementasi konkret berbeda dari ketergantungan Anda saat runtime.
Untuk alasan itu (antara lain), Anda mungkin akan melihat pewarisan yang lebih sering digunakan dalam bentuk implementasi antarmuka atau kelas abstrak daripada pewarisan vanila.
Contoh (metaforis) dapat berupa:
"Saya memiliki kelas Snake dan saya ingin memasukkan sebagai bagian dari kelas itu apa yang terjadi ketika Snake menggigit. Saya akan tergoda untuk memiliki Ular mewarisi kelas BiterAnimal yang memiliki metode Bite () dan menimpa metode itu untuk mencerminkan gigitan berbisa Tapi Komposisi atas Warisan memperingatkan saya bahwa saya harus mencoba menggunakan komposisi sebagai gantinya ... Dalam kasus saya, ini dapat diterjemahkan ke dalam Snake yang memiliki anggota Bite. Kelas Bite dapat berupa abstrak (atau antarmuka) dengan beberapa subclass. Ini akan memungkinkan saya hal-hal yang baik seperti memiliki subkelas VenomousBite dan DryBite dan dapat mengubah gigitan pada contoh ular yang sama dengan usia ular yang bertambah. Ditambah menangani semua efek dari Gigitan di kelas terpisahnya sendiri dapat memungkinkan saya untuk menggunakannya kembali di kelas Frost , karena gigitan es tetapi bukan BiterAnimal, dan seterusnya ... "
sumber
Beberapa kemungkinan argumen untuk komposisi:
Komposisi sedikit lebih banyak bahasa / kerangka kerja
Warisan agnostik dan apa yang ditegakkan / butuhkan / aktifkan akan berbeda antara bahasa dalam hal apa sub / superclass memiliki akses ke dan apa implikasi kinerja itu mungkin memiliki metode virtual dll. Komposisi cukup mendasar dan membutuhkan sangat sedikit dukungan bahasa, dan dengan demikian implementasi lintas platform / kerangka kerja yang berbeda dapat berbagi pola komposisi dengan lebih mudah.
Komposisi adalah cara yang sangat sederhana dan taktil untuk membangun objek
Warisan relatif mudah dipahami, tetapi masih tidak mudah ditunjukkan dalam kehidupan nyata. Banyak objek dalam kehidupan nyata dapat dipecah menjadi beberapa bagian dan tersusun. Katakanlah sepeda dapat dibangun menggunakan dua roda, bingkai, kursi, rantai dll. Mudah dijelaskan dengan komposisi. Sedangkan dalam metafora pewarisan, Anda bisa mengatakan bahwa sepeda memanjang dengan sepatu roda, agak layak tetapi masih jauh dari gambaran sebenarnya daripada komposisi (jelas ini bukan contoh pewarisan yang sangat baik, tetapi intinya tetap sama). Bahkan kata pewarisan (setidaknya dari sebagian besar penutur bahasa Inggris AS yang saya harapkan) secara otomatis memanggil makna di sepanjang baris "Sesuatu diturunkan dari kerabat yang sudah meninggal" yang memiliki beberapa korelasi dengan artinya dalam perangkat lunak, tetapi masih hanya cocok longgar.
Komposisi hampir selalu lebih fleksibel.
Menggunakan komposisi Anda selalu dapat memilih untuk mendefinisikan perilaku Anda sendiri atau hanya mengekspos bagian itu dari bagian-bagian yang Anda buat. Dengan cara ini Anda tidak menghadapi batasan apa pun yang mungkin dikenakan oleh hierarki warisan (virtual vs non-virtual, dll.)
Jadi, bisa jadi karena Komposisi secara alami adalah metafora yang lebih sederhana yang memiliki kendala teoretis lebih sedikit daripada pewarisan. Lebih lanjut, alasan-alasan khusus ini mungkin lebih jelas selama waktu desain, atau mungkin menonjol ketika berhadapan dengan beberapa titik sakit dari warisan.
Penafian:
Jelas ini bukan jalan yang jelas. Setiap desain pantas dievaluasi beberapa pola / alat. Warisan banyak digunakan, memiliki banyak manfaat dan berkali-kali lebih elegan dari komposisi. Ini hanya beberapa kemungkinan alasan yang bisa digunakan ketika memilih komposisi.
sumber
Mungkin Anda baru saja memperhatikan orang mengatakan ini dalam beberapa bulan terakhir, tetapi telah diketahui oleh programmer yang baik lebih lama dari itu. Saya tentu mengatakannya di mana yang sesuai selama sekitar satu dekade.
Inti dari konsep ini adalah bahwa ada overhead konseptual yang besar untuk warisan. Ketika Anda menggunakan warisan, maka setiap panggilan metode tunggal memiliki pengiriman implisit di dalamnya. Jika Anda memiliki pohon warisan yang dalam, atau banyak pengiriman, atau (bahkan lebih buruk) keduanya, maka mencari tahu di mana metode tertentu akan dikirim dalam panggilan tertentu dapat menjadi PITA kerajaan. Itu membuat alasan yang benar tentang kode lebih kompleks, dan itu membuat debugging lebih sulit.
Biarkan saya memberikan contoh sederhana untuk diilustrasikan. Misalkan jauh di dalam pohon warisan, seseorang bernama metode
foo
. Kemudian orang lain datang dan menambahkanfoo
di atas pohon, tetapi melakukan sesuatu yang berbeda. (Kasus ini lebih umum dengan pewarisan berganda.) Sekarang orang yang bekerja di kelas akar telah melanggar kelas anak yang tidak jelas dan mungkin tidak menyadarinya. Anda dapat memiliki cakupan 100% dengan tes unit dan tidak melihat kerusakan ini karena orang di atas tidak akan berpikir untuk menguji kelas anak, dan tes untuk kelas anak tidak berpikir untuk menguji metode baru yang dibuat di atas . (Memang ada cara untuk menulis tes unit yang akan menangkap ini, tetapi ada juga kasus di mana Anda tidak dapat dengan mudah menulis tes seperti itu.)Sebaliknya ketika Anda menggunakan komposisi, pada setiap panggilan biasanya lebih jelas apa tujuan pengiriman Anda. (Oke, jika Anda menggunakan inversi kontrol, misalnya dengan injeksi ketergantungan, mencari tahu kemana panggilan juga bisa menjadi masalah. Tapi biasanya lebih mudah untuk mencari tahu.) Ini membuat alasan tentang hal itu lebih mudah. Sebagai bonus, komposisi menghasilkan metode terpisah satu sama lain. Contoh di atas tidak boleh terjadi di sana karena kelas anak akan pindah ke beberapa komponen yang tidak jelas, dan tidak pernah ada pertanyaan tentang apakah panggilan ke
foo
ditujukan untuk komponen yang tidak jelas atau objek utama.Sekarang Anda benar sekali bahwa warisan dan komposisi adalah dua alat yang sangat berbeda yang melayani dua jenis benda yang berbeda. Memang warisan membawa overhead konseptual, tetapi ketika itu adalah alat yang tepat untuk pekerjaan itu, ia membawa overhead konseptual yang lebih sedikit daripada mencoba untuk tidak menggunakannya dan lakukan dengan tangan apa fungsinya untuk Anda. Tidak ada yang tahu apa yang mereka lakukan akan mengatakan bahwa Anda tidak boleh menggunakan warisan. Tetapi pastikan itu adalah hal yang benar untuk dilakukan.
Sayangnya banyak pengembang belajar tentang perangkat lunak berorientasi objek, belajar tentang warisan, dan kemudian pergi menggunakan kapak baru mereka sesering mungkin. Yang berarti bahwa mereka mencoba menggunakan warisan di mana komposisi adalah alat yang tepat. Semoga mereka akan belajar lebih baik pada waktunya, tetapi seringkali ini tidak terjadi sampai setelah beberapa anggota badan diangkat, dll. Memberitahu mereka di muka bahwa itu adalah ide yang buruk cenderung mempercepat proses belajar dan mengurangi cedera.
sumber
Ini adalah reaksi terhadap pengamatan bahwa pemula OO cenderung menggunakan warisan ketika mereka tidak perlu. Warisan tentu bukan hal yang buruk, tetapi bisa digunakan secara berlebihan. Jika satu kelas hanya membutuhkan fungsionalitas dari yang lain, maka komposisi mungkin akan berfungsi. Dalam keadaan lain, warisan akan berfungsi dan komposisinya tidak.
Mewarisi dari kelas menyiratkan banyak hal. Ini menyiratkan bahwa Turunan adalah jenis Basis (lihat prinsip Pergantian Liskov untuk detail berdarah), di mana setiap kali Anda menggunakan Basis itu akan masuk akal untuk menggunakan Turunan. Ini memberikan akses yang diturunkan ke anggota yang dilindungi dan fungsi anggota Base. Ini adalah hubungan yang erat, artinya memiliki kopling yang tinggi, dan perubahan yang satu lebih cenderung membutuhkan perubahan yang lain.
Coupling adalah hal yang buruk. Itu membuat program lebih sulit untuk dipahami dan dimodifikasi. Hal lain dianggap sama, Anda harus selalu memilih opsi dengan kopling lebih sedikit.
Oleh karena itu, jika salah satu komposisi atau warisan akan melakukan pekerjaan secara efektif, pilih komposisi, karena kopling lebih rendah. Jika komposisi tidak akan melakukan pekerjaan dengan efektif, dan warisan akan, pilihlah warisan, karena Anda harus.
sumber
Inilah dua sen saya (di luar semua poin bagus yang telah diangkat):
IMHO, ini bermuara pada kenyataan bahwa sebagian besar programmer tidak benar-benar mendapatkan warisan, dan akhirnya berlebihan, sebagian sebagai hasil dari bagaimana konsep ini diajarkan. Konsep ini ada sebagai cara untuk mencoba mencegah mereka dari melakukannya secara berlebihan, alih-alih berfokus pada mengajar mereka bagaimana melakukannya dengan benar.
Siapa pun yang telah menghabiskan waktu mengajar atau membimbing tahu bahwa inilah yang sering terjadi, terutama dengan pengembang baru yang memiliki pengalaman dalam paradigma lain:
Pengembang ini awalnya merasa bahwa warisan adalah konsep yang menakutkan ini. Jadi mereka akhirnya membuat jenis dengan tumpang tindih antarmuka (misalnya, perilaku yang ditentukan sama tanpa subtyping umum), dan dengan global untuk menerapkan fungsionalitas umum.
Kemudian (sering sebagai hasil dari pengajaran yang terlalu bersemangat), mereka belajar tentang manfaat warisan, tetapi sering diajarkan sebagai solusi terbaik untuk digunakan kembali. Mereka berakhir dengan persepsi bahwa perilaku bersama harus dibagi melalui warisan. Ini karena fokusnya sering pada implementasi warisan daripada subtyping.
Dalam 80% kasus itu cukup baik. Tetapi 20% lainnya adalah di mana masalahnya dimulai. Demi menghindari penulisan ulang dan untuk memastikan mereka telah mengambil keuntungan dari implementasi bersama, mereka mulai merancang hierarki mereka di sekitar implementasi yang dimaksud daripada abstraksi yang mendasarinya. "Stack mewarisi dari daftar tertaut ganda" adalah contoh yang bagus untuk ini.
Sebagian besar guru tidak bersikeras memperkenalkan konsep antarmuka pada titik ini, karena ini merupakan konsep lain, atau karena (dalam C ++) Anda harus memalsukan mereka menggunakan kelas abstrak dan pewarisan berganda yang tidak diajarkan pada tahap ini. Di Jawa, banyak guru tidak membedakan "no multiple inheritance" atau "multiple inheritance evil" dari pentingnya interface.
Semua ini diperparah oleh fakta bahwa kita telah belajar banyak tentang keindahan tidak harus menulis kode berlebihan dengan warisan implementasi, sehingga banyak kode delegasi langsung terlihat tidak wajar. Jadi komposisi terlihat berantakan, itulah sebabnya kita perlu aturan praktis ini untuk mendorong programmer baru untuk menggunakannya (yang mereka lakukan juga).
sumber
Dalam salah satu komentar, Mason menyebutkan bahwa suatu hari kita akan berbicara tentang Warisan yang dianggap berbahaya .
Saya berharap begitu.
Masalah dengan pewarisan sekaligus sederhana, dan mematikan, itu tidak menghormati gagasan bahwa satu konsep harus memiliki satu fungsi.
Di sebagian besar OO-bahasa, ketika mewarisi dari kelas dasar Anda:
Dan di sini menjadi masalah.
Ini bukan satu-satunya pendekatan, meskipun bahasa 00 sebagian besar terjebak dengan itu. Untungnya antarmuka / kelas abstrak ada di dalamnya.
Juga, kurangnya kemudahan untuk melakukan sebaliknya berkontribusi membuatnya sebagian besar digunakan: terus terang, bahkan mengetahui hal ini, akankah Anda mewarisi dari antarmuka dan menanamkan kelas dasar dengan komposisi, mendelegasikan sebagian besar panggilan metode? Akan jauh lebih baik, Anda bahkan akan diperingatkan jika tiba-tiba sebuah metode baru muncul di antarmuka dan harus memilih, secara sadar, bagaimana menerapkannya.
Sebagai titik tandingan,
Haskell
hanya izinkan untuk menggunakan Prinsip Liskov ketika "diturunkan" dari antarmuka murni (disebut konsep) (1). Anda tidak dapat berasal dari kelas yang ada, hanya komposisi yang memungkinkan Anda untuk menyematkan datanya.(1) konsep dapat memberikan default yang masuk akal untuk implementasi, tetapi karena mereka tidak memiliki data, default ini hanya dapat didefinisikan dalam hal metode lain yang diusulkan oleh konsep atau dalam hal konstanta.
sumber
Jawaban sederhananya adalah: warisan memiliki penggandaan lebih besar dari komposisi. Diberikan dua opsi, dengan kualitas yang setara, pilih salah satu yang kurang digabungkan.
sumber
Saya pikir saran semacam ini seperti mengatakan "lebih suka mengemudi daripada terbang". Artinya, pesawat memiliki segala macam keunggulan dibandingkan mobil, tetapi itu datang dengan kompleksitas tertentu. Jadi, jika banyak orang mencoba terbang dari pusat kota ke pinggiran kota, saran yang benar-benar perlu mereka dengar adalah bahwa mereka tidak perlu terbang, dan pada kenyataannya, terbang hanya akan membuatnya lebih rumit dalam jangka panjang, bahkan jika tampaknya keren / efisien / mudah dalam jangka pendek. Sedangkan bila Anda tidak perlu terbang, itu umumnya seharusnya jelas.
Demikian juga, warisan dapat melakukan hal-hal komposisi tidak bisa, tetapi Anda harus menggunakannya ketika Anda membutuhkannya, dan bukan ketika Anda tidak. Jadi, jika Anda tidak pernah tergoda untuk hanya menganggap Anda membutuhkan warisan ketika Anda tidak, maka Anda tidak perlu saran "lebih suka komposisi". Tetapi banyak orang melakukannya , dan memang membutuhkan nasihat itu.
Seharusnya secara implisit bahwa ketika Anda benar-benar TIDAK membutuhkan warisan, sudah jelas, dan Anda harus menggunakannya kemudian.
Juga, jawaban Steven Lowe. Sungguh, sungguh itu.
sumber
Warisan pada dasarnya tidak buruk, dan komposisi pada dasarnya tidak baik. Mereka hanyalah alat yang dapat digunakan programmer OO untuk merancang perangkat lunak.
Ketika Anda melihat kelas, apakah ia melakukan lebih dari yang seharusnya ( SRP )? Apakah itu menduplikasi fungsi yang tidak perlu ( KERING ), atau itu terlalu tertarik pada properti atau metode kelas lain ( Fitur Envy ) ?. Jika kelas tersebut melanggar semua konsep ini (dan mungkin lebih), apakah ia berusaha menjadi Kelas Dewa . Ini adalah sejumlah masalah yang dapat terjadi ketika mendesain perangkat lunak, tidak ada yang merupakan masalah warisan, tetapi yang dapat dengan cepat membuat sakit kepala besar dan ketergantungan yang rapuh di mana polimorfisme juga telah diterapkan.
Akar masalahnya kemungkinan adalah kurangnya pemahaman tentang pewarisan, dan lebih dari salah satu pilihan yang buruk dalam hal desain, atau mungkin tidak mengenali "bau kode" yang berkaitan dengan kelas yang tidak mematuhi Prinsip Tanggung Jawab Tunggal . Substitusi Polimorfisme dan Liskov tidak perlu dibuang demi komposisi. Polimorfisme itu sendiri dapat diterapkan tanpa bergantung pada warisan, ini semua adalah konsep yang cukup komplementer. Jika diterapkan dengan penuh pertimbangan. Kuncinya adalah bertujuan untuk menjaga kode Anda sederhana, dan bersih, dan untuk tidak menyerah terlalu khawatir tentang jumlah kelas yang perlu Anda buat untuk membuat desain sistem yang kuat.
Sejauh masalah komposisi lebih disukai daripada warisan, ini benar-benar hanya kasus lain dari aplikasi bijaksana dari elemen desain yang paling masuk akal dalam hal masalah yang sedang dipecahkan. Jika Anda tidak perlu mewarisi perilaku, maka sebaiknya Anda tidak melakukannya karena komposisi akan membantu menghindari ketidakcocokan dan upaya refactoring besar di kemudian hari. Jika di sisi lain Anda menemukan bahwa Anda mengulang banyak kode sehingga semua duplikasi berpusat pada sekelompok kelas yang sama, maka mungkin menciptakan nenek moyang yang sama akan membantu mengurangi jumlah panggilan dan kelas yang identik Anda mungkin perlu mengulang di antara setiap kelas. Dengan demikian, Anda menyukai komposisi, namun Anda tidak menganggap bahwa warisan tidak pernah berlaku.
sumber
Saya percaya, kutipan yang sebenarnya datang dari Joshua Bloch's "Java Efektif", di mana itu adalah judul dari salah satu bab. Dia mengklaim bahwa warisan aman dalam sebuah paket dan di mana pun kelas telah dirancang dan didokumentasikan secara khusus untuk perpanjangan. Dia mengklaim, seperti orang lain telah mencatat, bahwa warisan istirahat enkapsulasi karena subkelas tergantung pada detail implementasi superclass-nya. Ini, katanya, mengarah pada kerapuhan.
Walaupun dia tidak menyebutkannya, bagi saya kelihatannya satu warisan di Jawa berarti komposisi memberi Anda lebih banyak fleksibilitas daripada warisan.
sumber