Ada dua aliran pemikiran tentang cara terbaik untuk memperluas, meningkatkan, dan menggunakan kembali kode dalam sistem berorientasi objek:
Warisan: memperluas fungsi kelas dengan membuat subclass. Timpa anggota superclass di subclass untuk memberikan fungsionalitas baru. Buat metode abstrak / virtual untuk memaksa subclass untuk "mengisi-kosong" ketika superclass menginginkan antarmuka tertentu tetapi agnostik tentang implementasinya.
Agregasi: membuat fungsionalitas baru dengan mengambil kelas lain dan menggabungkannya ke kelas baru. Pasang antarmuka umum ke kelas baru ini untuk interoperabilitas dengan kode lain.
Apa manfaat, biaya, dan konsekuensi dari masing-masing? Apakah ada alternatif lain?
Saya melihat debat ini muncul secara teratur, tapi saya tidak berpikir itu sudah ditanyakan pada Stack Overflow (meskipun ada beberapa diskusi terkait). Ada juga kurangnya hasil Google yang bagus untuk itu.
sumber
Jawaban:
Ini bukan masalah mana yang terbaik, tetapi kapan harus menggunakan apa.
Dalam kasus-kasus 'normal', pertanyaan sederhana sudah cukup untuk mengetahui apakah kita membutuhkan warisan atau agregasi.
Namun, ada area abu-abu besar. Jadi kita perlu beberapa trik lain.
Untuk memotongnya. Kita harus menggunakan agregasi jika bagian dari antarmuka tidak digunakan atau harus diubah untuk menghindari situasi yang tidak masuk akal. Kita hanya perlu menggunakan warisan, jika kita membutuhkan hampir semua fungsi tanpa perubahan besar. Dan jika ragu, gunakan Agregasi.
Kemungkinan lain untuk, kasus bahwa kita memiliki kelas yang membutuhkan bagian dari fungsionalitas kelas asli, adalah untuk membagi kelas asli menjadi kelas root dan sub kelas. Dan biarkan kelas baru mewarisi dari kelas root. Tetapi Anda harus berhati-hati dengan ini, bukan untuk menciptakan pemisahan yang tidak logis.
Mari kita tambahkan contoh. Kami memiliki kelas 'Dog' dengan metode: 'Eat', 'Walk', 'Bark', 'Play'.
Kita sekarang membutuhkan kelas 'Kucing', yang membutuhkan 'Makan', 'Jalan', 'Purr', dan 'Mainkan'. Jadi pertama-tama cobalah untuk memperpanjang dari Anjing.
Terlihat, baiklah, tapi tunggu. Kucing ini bisa menggonggong (Pecinta Kucing akan membunuhku untuk itu). Dan seekor kucing menggonggong melanggar prinsip-prinsip alam semesta. Jadi kita perlu mengganti metode Bark agar tidak melakukan apa-apa.
Ok, ini berhasil, tapi baunya buruk. Jadi mari kita coba agregasi:
Ok, ini bagus. Kucing ini tidak menggonggong lagi, bahkan tidak diam. Tetapi masih memiliki anjing internal yang ingin keluar. Jadi mari kita coba solusi nomor tiga:
Ini jauh lebih bersih. Tidak ada anjing internal. Dan kucing dan anjing berada pada level yang sama. Kami bahkan dapat memperkenalkan hewan peliharaan lain untuk memperluas model. Kecuali kalau itu ikan, atau sesuatu yang tidak bisa berjalan. Dalam hal ini kita perlu refactor lagi. Tetapi itu adalah sesuatu untuk waktu yang lain.
sumber
makeSound
dan membiarkan setiap subclass mengimplementasikan jenis suara mereka sendiri. Ini kemudian akan membantu dalam situasi di mana Anda memiliki banyak hewan peliharaan dan lakukanfor_each pet in the list of pets, pet.makeSound
.Pada awal GOF mereka menyatakan
Ini dibahas lebih lanjut di sini
sumber
Perbedaannya biasanya dinyatakan sebagai perbedaan antara "adalah" dan "memiliki". Warisan, hubungan "adalah", dirangkum dengan baik dalam Prinsip Substitusi Liskov . Agregasi, hubungan "memiliki", hanya itu - itu menunjukkan bahwa objek agregasi memiliki salah satu objek agregat.
Perbedaan lebih lanjut juga ada - warisan pribadi dalam C ++ menunjukkan hubungan "diterapkan dalam hal", yang juga dapat dimodelkan dengan agregasi objek anggota (yang tidak terpapar) juga.
sumber
Inilah argumen saya yang paling umum:
Dalam sistem berorientasi objek apa pun, ada dua bagian untuk kelas apa pun:
Its antarmuka : "wajah publik" dari objek. Ini adalah seperangkat kemampuan yang diumumkannya ke seluruh dunia. Dalam banyak bahasa, himpunan didefinisikan dengan baik menjadi "kelas". Biasanya ini adalah tanda tangan metode objek, meskipun sedikit bervariasi berdasarkan bahasa.
Its implementasi : "di belakang layar" pekerjaan yang objek tidak memuaskan antarmuka dan menyediakan fungsionalitas. Ini biasanya kode dan data anggota objek.
Salah satu prinsip dasar dari OOP adalah bahwa implementasinya dirangkum (yaitu: tersembunyi) di dalam kelas; satu-satunya hal yang harus dilihat orang luar adalah antarmuka.
Ketika subclass mewarisi dari subclass, biasanya mewarisi baik pelaksanaan dan antarmuka. Ini, pada gilirannya, berarti Anda dipaksa untuk menerima keduanya sebagai kendala pada kelas Anda.
Dengan agregasi, Anda bisa memilih implementasi atau antarmuka, atau keduanya - tetapi Anda tidak dipaksa untuk melakukannya. Fungsi suatu objek diserahkan kepada objek itu sendiri. Itu dapat tunduk pada objek lain seperti yang diinginkan, tetapi pada akhirnya bertanggung jawab untuk dirinya sendiri. Dalam pengalaman saya, ini mengarah ke sistem yang lebih fleksibel: yang lebih mudah untuk dimodifikasi.
Jadi, setiap kali saya mengembangkan perangkat lunak berorientasi objek, saya hampir selalu lebih suka agregasi daripada warisan.
sumber
Saya memberi jawaban untuk "Is a" vs "Has a": mana yang lebih baik? .
Pada dasarnya saya setuju dengan orang lain: gunakan pewarisan hanya jika kelas turunan Anda benar - benar tipe yang Anda perluas, bukan hanya karena berisi data yang sama. Ingatlah bahwa pewarisan berarti subkelas memperoleh metode dan juga data.
Apakah masuk akal bagi kelas turunan Anda untuk memiliki semua metode superclass? Atau apakah Anda hanya diam-diam berjanji pada diri sendiri bahwa metode itu harus diabaikan di kelas turunan? Atau apakah Anda menemukan diri Anda mengganti metode dari superclass, menjadikannya no-ops sehingga tidak ada yang menyebut mereka secara tidak sengaja? Atau memberikan petunjuk ke alat pembuatan dokumen API Anda untuk menghilangkan metode dari dokumen?
Itu adalah petunjuk kuat bahwa agregasi adalah pilihan yang lebih baik dalam kasus itu.
sumber
Saya melihat banyak tanggapan "is-a vs has-a; mereka berbeda secara konseptual" tentang ini dan pertanyaan terkait.
Satu hal yang saya temukan dalam pengalaman saya adalah mencoba menentukan apakah suatu hubungan "is-a" atau "has-a" pasti gagal. Bahkan jika Anda dapat menentukan dengan tepat objek tersebut sekarang, mengubah persyaratan berarti bahwa Anda mungkin akan salah di beberapa titik di masa depan.
Hal lain yang saya temukan adalah bahwa sangat sulit untuk mengkonversi dari pewarisan ke agregasi begitu ada banyak kode yang ditulis di sekitar hierarki warisan. Hanya beralih dari superclass ke antarmuka berarti mengubah hampir setiap subkelas dalam sistem.
Dan, seperti yang saya sebutkan di bagian lain dalam posting ini, agregasi cenderung kurang fleksibel daripada warisan.
Jadi, Anda memiliki badai argumen yang sempurna terhadap warisan setiap kali Anda harus memilih satu atau yang lain:
Jadi, saya cenderung memilih agregasi - bahkan ketika tampaknya ada hubungan is-a yang kuat.
sumber
Pertanyaannya biasanya diutarakan sebagai Komposisi vs. Warisan , dan telah diajukan di sini sebelumnya.
sumber
Saya ingin berkomentar tentang pertanyaan awal, tetapi 300 karakter menggigit [; <).
Saya pikir kita perlu berhati-hati. Pertama, ada lebih banyak rasa daripada dua contoh agak spesifik yang dibuat dalam pertanyaan.
Juga, saya sarankan bahwa tidak membingungkan tujuan dengan instrumen. Seseorang ingin memastikan bahwa teknik atau metodologi yang dipilih mendukung pencapaian tujuan utama, tetapi saya tidak merasa di luar konteks yang mana teknik-adalah-diskusi terbaik sangat berguna. Memang membantu untuk mengetahui perangkap dari berbagai pendekatan bersama dengan sweet spot mereka yang jelas.
Misalnya, apa yang ingin Anda capai, apa yang Anda miliki untuk memulai, dan apa saja kendalanya?
Apakah Anda membuat kerangka kerja komponen, bahkan yang bertujuan khusus? Apakah antarmuka dapat dipisahkan dari implementasi dalam sistem pemrograman atau apakah itu dicapai dengan praktik menggunakan jenis teknologi yang berbeda? Bisakah Anda memisahkan struktur warisan antarmuka (jika ada) dari struktur warisan kelas yang mengimplementasikannya? Apakah penting untuk menyembunyikan struktur kelas suatu implementasi dari kode yang bergantung pada antarmuka yang diberikan oleh implementasi? Apakah ada beberapa implementasi yang dapat digunakan pada saat yang sama atau apakah variasi lebih dari waktu ke waktu sebagai konsekuensi dari pemeliharaan dan peningkatan perangkat? Ini dan lebih banyak perlu dipertimbangkan sebelum Anda terpaku pada alat atau metodologi.
Akhirnya, apakah penting untuk mengunci perbedaan dalam abstraksi dan bagaimana Anda memikirkannya (seperti pada is-a versus has-a) dengan fitur-fitur berbeda dari teknologi OO? Mungkin demikian, jika itu membuat struktur konseptual konsisten dan dapat dikelola untuk Anda dan orang lain. Tetapi adalah bijaksana untuk tidak diperbudak oleh hal itu dan contortions yang akhirnya Anda buat. Mungkin yang terbaik adalah mundur satu tingkat dan tidak terlalu kaku (tapi tinggalkan narasi yang baik agar orang lain tahu apa yang terjadi). [Saya mencari apa yang membuat bagian tertentu dari suatu program dapat dijelaskan, tetapi beberapa kali saya mencari keanggunan ketika ada kemenangan yang lebih besar. Tidak selalu ide terbaik.]
Saya seorang purist antarmuka, dan saya tertarik pada berbagai masalah dan pendekatan di mana purisme antarmuka sesuai, apakah membangun kerangka kerja Java atau mengatur beberapa implementasi COM. Itu tidak membuatnya cocok untuk semuanya, bahkan tidak dekat dengan segalanya, meskipun aku bersumpah demi itu. (Saya memiliki beberapa proyek yang tampaknya memberikan contoh tandingan yang serius terhadap kemurnian antarmuka, sehingga akan menarik untuk melihat bagaimana saya berhasil mengatasinya.)
sumber
Saya akan membahas bagian mana-ini-mungkin-berlaku. Berikut adalah contoh keduanya, dalam skenario permainan. Misalkan, ada permainan yang memiliki berbagai jenis tentara. Setiap prajurit dapat memiliki ransel yang dapat menampung hal-hal yang berbeda.
Warisan di sini? Ada baret laut, hijau & penembak jitu. Ini adalah tipe prajurit. Jadi, ada kelas dasar Soldier dengan Marine, Green Baret & Sniper sebagai kelas turunan
Agregasi di sini? Ransel itu bisa berisi granat, senjata (berbagai jenis), pisau, medikit, dll. Seorang prajurit dapat dilengkapi dengan semua ini pada suatu titik waktu tertentu, ditambah ia juga dapat memiliki rompi anti peluru yang bertindak sebagai baju besi ketika diserang dan pasukannya. cedera berkurang hingga persentase tertentu. Kelas prajurit berisi objek kelas rompi antipeluru dan kelas ransel yang berisi referensi ke item ini.
sumber
Saya pikir ini bukan perdebatan. Hanya saja:
Keduanya memiliki tempat masing-masing, tetapi warisan lebih berisiko.
Meskipun tentu saja tidak masuk akal untuk memiliki kelas Shape 'having-a' Point dan Square. Di sini warisan sudah jatuh tempo.
Orang-orang cenderung berpikir tentang pewarisan terlebih dahulu ketika mencoba merancang sesuatu yang dapat diperluas, itulah yang salah.
sumber
Favor terjadi ketika kedua kandidat lolos. A dan B adalah opsi dan Anda menyukai A. Alasannya adalah komposisi menawarkan lebih banyak kemungkinan ekstensi / fleksibilitas daripada generalisasi. Ekstensi / fleksibilitas ini sebagian besar merujuk pada runtime / fleksibilitas dinamis.
Manfaatnya tidak segera terlihat. Untuk melihat manfaatnya, Anda harus menunggu permintaan perubahan tak terduga berikutnya. Jadi dalam kebanyakan kasus mereka yang bertahan pada generalisasi gagal jika dibandingkan dengan mereka yang memeluk komposisi (kecuali satu kasus yang jelas disebutkan kemudian). Karena itu aturannya. Dari sudut pandang pembelajaran jika Anda dapat mengimplementasikan injeksi ketergantungan dengan sukses, maka Anda harus tahu mana yang disukai dan kapan. Aturan membantu Anda dalam membuat keputusan juga; jika Anda tidak yakin maka pilih komposisi.
Ringkasan: Komposisi: Kopling dikurangi dengan hanya memiliki beberapa hal kecil yang Anda tancapkan ke sesuatu yang lebih besar, dan objek yang lebih besar hanya memanggil objek yang lebih kecil kembali. Generalisasi: Dari sudut pandang API, mendefinisikan bahwa suatu metode dapat diganti adalah komitmen yang lebih kuat daripada mendefinisikan bahwa suatu metode dapat dipanggil. (Sangat sedikit kesempatan ketika Generalisasi menang). Dan jangan pernah lupa bahwa dengan komposisi Anda menggunakan warisan juga, dari antarmuka, bukan kelas besar
sumber
Kedua pendekatan digunakan untuk menyelesaikan masalah yang berbeda. Anda tidak selalu perlu mengumpulkan lebih dari dua kelas atau lebih ketika mewarisi dari satu kelas.
Kadang-kadang Anda harus mengagregasi satu kelas karena kelas itu disegel atau memiliki anggota non-virtual yang perlu Anda sadap sehingga Anda membuat lapisan proksi yang jelas tidak valid dalam hal pewarisan tetapi selama kelas yang Anda proksi memiliki antarmuka Anda dapat berlangganan ini dapat bekerja dengan baik
sumber