it-swarm-id.com

Kode Bau: Penyalahgunaan Warisan

Sudah diterima secara umum di komunitas OO bahwa seseorang harus "mendukung komposisi daripada warisan". Di sisi lain, pewarisan memang memberikan polimorfisme dan cara pendelegasian segala sesuatunya langsung ke kelas dasar kecuali secara eksplisit ditimpa dan oleh karena itu sangat mudah dan berguna.

Tanda paling pasti dan IMHO tentang penyalahgunaan warisan adalah pelanggaran Prinsip Pergantian Liskov . Apa saja tanda-tanda lain bahwa pewarisan adalah Alat yang Salah untuk Pekerjaan bahkan jika tampaknya nyaman?

52
dsimcha

Saat mewarisi hanya untuk mendapatkan fungsionalitas, Anda mungkin menyalahgunakan warisan. Ini mengarah pada penciptaan Obyek Tuhan .

Warisan itu sendiri tidak menjadi masalah selama Anda melihat hubungan nyata antara kelas-kelas (seperti contoh klasik, seperti Dog extends Animal) dan Anda tidak menggunakan metode pada kelas induk yang tidak masuk akal pada sebagian kelas itu. anak-anak (misalnya, menambahkan metode Bark () di kelas Animal tidak akan masuk akal dalam kelas Fish yang memperpanjang Animal).

Jika suatu kelas membutuhkan fungsionalitas, gunakan komposisi (mungkin menyuntikkan penyedia fungsionalitas ke konstruktor?). Jika suatu kelas perlu MENJADI seperti yang lain, maka gunakan warisan.

48
Fernando

Saya akan mengatakan, mengambil risiko ditembak jatuh, bahwa Warisan adalah bau kode itu sendiri :)

Masalah dengan pewarisan adalah bahwa ia dapat digunakan untuk dua tujuan ortogonal:

  • antarmuka (untuk polimorfisme)
  • implementasi (untuk penggunaan kembali kode)

Memiliki mekanisme tunggal untuk mendapatkan keduanya adalah apa yang mengarah pada "penyalahgunaan warisan" di tempat pertama, karena sebagian besar orang berharap warisan tentang antarmuka, tetapi mungkin digunakan untuk mendapatkan implementasi standar bahkan oleh programmer yang berhati-hati (itu hanya sangat mudah untuk abaikan ini ...)

Faktanya, bahasa modern seperti Haskell atau Go, telah meninggalkan warisan untuk memisahkan kedua masalah tersebut.

Kembali ke jalur:

Oleh karena itu, pelanggaran terhadap Prinsip Liskov adalah tanda paling pasti, karena itu berarti bahwa "antarmuka" bagian dari kontrak tidak dihormati.

Namun bahkan ketika antarmuka dihormati, Anda mungkin memiliki objek yang diwarisi dari kelas dasar "gemuk" hanya karena salah satu metode dianggap berguna.

Karenanya Prinsip Liskov itu sendiri tidak cukup, yang perlu Anda ketahui adalah apakah polimorfisme itu digunakan. Jika tidak, maka tidak ada gunanya mewarisi sejak awal, itu penyalahgunaan.

Ringkasan:

  • menegakkan Liskov Principle
  • periksa itu benar-benar digunakan

Cara mengatasinya:

Memberlakukan pemisahan keprihatinan yang jelas:

  • hanya mewarisi dari antarmuka
  • gunakan komposisi untuk mendelegasikan implementasi

berarti bahwa setidaknya diperlukan beberapa kali penekanan tombol untuk setiap metode, dan tiba-tiba orang mulai berpikir apakah ide yang bagus untuk menggunakan kembali kelas "gemuk" ini hanya untuk sebagian kecil saja.

39
Matthieu M.

Apakah ini benar-benar "diterima secara umum"? Itu adalah ide yang pernah saya dengar beberapa kali, tetapi ini bukan prinsip yang diakui secara universal. Seperti yang baru saja Anda tunjukkan, warisan dan polimorfisme adalah keunggulan dari OOP. Tanpa mereka, Anda hanya punya pemrograman prosedural dengan sintaks konyol.

Komposisi adalah alat untuk membangun objek dengan cara tertentu. Warisan adalah alat untuk membangun objek dengan cara yang berbeda. Tidak ada yang salah dengan mereka; Anda hanya perlu tahu bagaimana keduanya bekerja dan kapan waktu yang tepat untuk menggunakan setiap gaya.

14
Mason Wheeler

Kekuatan warisan adalah penggunaan kembali. Antarmuka, data, perilaku, kolaborasi. Ini adalah pola yang berguna untuk menyampaikan atribut ke kelas baru.

Kelemahan menggunakan pewarisan adalah usabilitas ulang. Antarmuka, data, dan perilaku dienkapsulasi dan dikirim secara massal, menjadikannya proposisi semua atau tidak sama sekali. Masalah menjadi sangat jelas ketika hierarki menjadi lebih dalam, karena properti yang mungkin berguna dalam abstrak menjadi kurang begitu dan mulai mengaburkan properti di tingkat lokal.

Setiap kelas yang menggunakan objek komposisi harus lebih atau kurang menerapkan kembali kode yang sama untuk berinteraksi dengannya. Meskipun duplikasi ini memerlukan beberapa pelanggaran KERING, itu memberi kebebasan yang lebih besar bagi desainer untuk memilih dan memilih properti kelas yang paling berarti bagi domain mereka. Ini sangat menguntungkan ketika kelas menjadi lebih terspesialisasi.

Secara umum, itu harus lebih disukai untuk membuat kelas melalui komposisi, dan kemudian mengkonversi ke kelas-kelas warisan dengan sebagian besar sifat mereka yang sama, meninggalkan perbedaan sebagai komposisi.

TKI, YAGNI (Kamu Tidak Perlu Warisan).

5
Huperniketes

Jika Anda mensubklasifikasi A ke kelas B tetapi tidak ada yang peduli jika B mewarisi dari A atau tidak, jadi itu pertanda bahwa Anda mungkin melakukan kesalahan .

3
tia

"Komposisi budi atas warisan" adalah pernyataan paling konyol. Keduanya adalah elemen penting dari OOP. Ini seperti mengatakan "Mendukung palu dari gergaji".

Tentu saja warisan dapat disalahgunakan, fitur bahasa apa pun dapat disalahgunakan, pernyataan bersyarat dapat disalahgunakan.

2
Chuck Stephanski

Mungkin yang paling jelas adalah ketika kelas pewarisan berakhir dengan setumpuk metode/properti yang tidak pernah digunakan dalam antarmuka yang terbuka. Dalam kasus terburuk, di mana kelas dasar memiliki anggota abstrak, Anda akan menemukan implementasi kosong (atau tidak berarti) dari anggota abstrak hanya untuk 'mengisi kekosongan'.

Lebih halus, kadang-kadang Anda melihat di mana dalam upaya untuk meningkatkan kode menggunakan kembali dua hal yang memiliki implementasi serupa telah diperas menjadi satu implementasi - meskipun kedua hal itu tidak terkait. Ini cenderung meningkatkan kopling kode dan menguraikan mereka ketika ternyata kesamaan itu hanya kebetulan bisa sangat menyakitkan kecuali itu terlihat lebih awal.

Diperbarui dengan beberapa contoh: Meskipun tidak secara langsung berkaitan dengan pemrograman seperti itu, saya pikir contoh terbaik untuk hal semacam ini adalah dengan lokalisasi/internasionalisasi. Dalam bahasa Inggris ada satu kata untuk "ya". Dalam bahasa lain dapat banyak, lebih banyak lagi untuk menyetujui secara tata bahasa dengan pertanyaan itu. Seseorang bisa berkata, ah mari kita beri string untuk "ya" dan itu bagus untuk banyak bahasa. Kemudian mereka menyadari bahwa string "ya" tidak benar-benar sesuai dengan "ya" tetapi sebenarnya konsep "jawaban afirmatif untuk pertanyaan ini" - fakta bahwa "ya" sepanjang waktu adalah kebetulan dan waktu yang dihemat hanya memiliki satu "ya" benar-benar hilang dalam mengurai kekacauan yang dihasilkan.

Saya telah melihat satu contoh buruk dari hal ini dalam basis kode kami di mana apa yang sebenarnya merupakan hubungan orangtua -> di mana orang tua dan anak memiliki properti dengan nama yang mirip dimodelkan dengan warisan. Tidak ada yang memperhatikan hal ini karena semuanya tampak berfungsi, maka perilaku anak (sebenarnya dasar) dan orang tua (subkelas) perlu menuju ke arah yang berbeda. Seperti keberuntungan, ini terjadi pada waktu yang salah ketika tenggat waktu menjulang - dalam upaya untuk men-tweak model di sana-sini kompleksitasnya (baik dalam hal logika dan duplikasi kode) langsung menembus atap. Itu juga sangat sulit untuk dipikirkan tentang/bekerja dengan karena kode itu tidak berhubungan dengan bagaimana bisnis berpikir atau berbicara tentang konsep-konsep yang diwakili oleh kelas-kelas ini. Pada akhirnya kami harus menggigit peluru dan melakukan beberapa refactoring besar yang bisa sepenuhnya dihindari jika lelaki asli tidak memutuskan bahwa beberapa properti terdengar serupa dengan nilai-nilai yang kebetulan sama mewakili konsep yang sama.

0
FinnNk

Saya pikir pertempuran seperti warisan vs komposisi, benar-benar hanya aspek dari pertempuran inti yang lebih dalam.

Pertarungan itu adalah: apa yang akan menghasilkan jumlah kode paling sedikit, untuk perluasan terbesar dalam peningkatan di masa mendatang?

Itu sebabnya pertanyaannya sangat sulit dijawab. Dalam satu bahasa bisa jadi pewarisan lebih cepat ditempatkan daripada komposisi daripada dalam bahasa lain, atau sebaliknya.

Atau bisa juga masalah spesifik yang Anda selesaikan dalam manfaat kode lebih dari kebijaksanaan daripada visi pertumbuhan jangka panjang. Itu adalah masalah bisnis yang sama dengan masalah pemrograman.

Jadi untuk kembali ke pertanyaan, pewarisan bisa salah jika itu membuat Anda mengenakan jaket lurus di beberapa titik di masa depan - atau jika itu membuat kode yang tidak perlu ada di sana (kelas mewarisi dari yang lain tanpa alasan, atau lebih buruk lagi tetapi kelas dasar yang mulai harus melakukan banyak tes bersyarat untuk anak-anak).

Ada beberapa situasi ketika pewarisan seharusnya lebih disukai daripada komposisi, dan perbedaannya jauh lebih jelas daripada soal gaya. Saya memparafrasekan dari Sutter dan Alexandrescu C++ Standar Pengkodean di sini karena salinan saya ada di rak buku saya yang sedang bekerja saat ini. Omong-omong membaca sangat dianjurkan.

Pertama-tama, warisan tidak dianjurkan ketika tidak perlu karena itu menciptakan hubungan yang sangat intim dan erat antara kelas dasar dan turunan yang dapat menyebabkan sakit kepala di jalan untuk pemeliharaan. Bukan hanya pendapat beberapa orang bahwa sintaksis untuk pewarisan membuat lebih sulit untuk dibaca atau sesuatu.

Yang sedang berkata, pewarisan didorong ketika Anda ingin kelas diturunkan itu sendiri digunakan kembali dalam konteks di mana kelas dasar saat ini sedang digunakan, tanpa harus mengubah kode dalam konteks itu. Misalnya, jika Anda memiliki kode yang mulai terlihat seperti if (type1) type1.foo(); else if (type2) type2.foo();, Anda mungkin memiliki kasus yang sangat bagus untuk melihat warisan.

Singkatnya, jika Anda hanya ingin menggunakan kembali metode kelas dasar, gunakan komposisi. Jika Anda ingin menggunakan kelas turunan dalam kode yang saat ini menggunakan kelas dasar, gunakan pewarisan.

0
Karl Bielefeldt