Dari: http://www.artima.com/lejava/articles/designprinciples4.html
Erich Gamma: Saya masih berpikir itu benar bahkan setelah sepuluh tahun. Warisan adalah cara yang keren untuk mengubah perilaku. Tetapi kita tahu bahwa itu rapuh, karena subclass dapat dengan mudah membuat asumsi tentang konteks di mana metode yang ditimpa dipanggil. Ada hubungan erat antara kelas dasar dan subkelas, karena konteks implisit di mana kode subkelas yang saya pasang akan dipanggil. Komposisi memiliki sifat yang lebih bagus. Kopling dikurangi dengan hanya memiliki beberapa hal yang lebih kecil yang Anda tancapkan ke sesuatu yang lebih besar, dan objek yang lebih besar hanya memanggil objek yang lebih kecil kembali. Dari sudut pandang API, mendefinisikan bahwa suatu metode dapat diganti adalah komitmen yang lebih kuat daripada mendefinisikan bahwa suatu metode dapat dipanggil.
Saya tidak mengerti apa maksudnya. Adakah yang bisa menjelaskannya?
Jika Anda menerbitkan fungsi normal, Anda memberikan kontrak satu sisi:
Apa fungsi jika dipanggil?
Jika Anda menerbitkan panggilan balik, Anda juga memberikan kontrak satu sisi:
Kapan dan bagaimana itu akan dipanggil?
Dan jika Anda menerbitkan fungsi yang tidak dapat ditimpa, keduanya sekaligus, jadi Anda memberikan kontrak dua sisi:
Kapan akan dipanggil, dan apa yang harus dilakukan jika dipanggil?
Bahkan jika pengguna tidak menyalahgunakan API Anda (dengan melanggar mereka bagian dari kontrak, yang mungkin mahal untuk mendeteksi), Anda dapat dengan mudah melihat bahwa kebutuhan kedua jauh lebih dokumentasi, dan segala sesuatu yang Anda dokumen adalah komitmen, yang batas pilihan Anda selanjutnya.
Contoh pengingkaran pada kontrak dua sisi tersebut adalah perpindahan dari
show
danhide
kesetVisible(boolean)
dalam java.awt.Component .sumber
show
danhide
masih ada, mereka hanya@Deprecated
. Jadi perubahan tidak merusak kode apa pun yang hanya memanggil mereka. Tetapi jika Anda menimpa mereka, penggantian Anda tidak akan dipanggil oleh klien yang bermigrasi ke 'setVisible' yang baru. (Saya tidak pernah menggunakan Swing, jadi saya tidak tahu seberapa umum menimpanya; tetapi karena itu sudah terjadi sejak lama, saya membayangkan alasan Deduplicator mengingatnya adalah karena itu menggigitnya dengan menyakitkan.)Jawaban Kilian Foth sangat bagus. Saya hanya ingin menambahkan contoh kanonik * mengapa ini menjadi masalah. Bayangkan kelas Point integer:
Sekarang mari kita sub-kelas menjadi titik 3D.
Sangat sederhana! Mari kita gunakan poin kami:
Anda mungkin bertanya-tanya mengapa saya memposting contoh yang begitu mudah. Inilah tangkapannya:
Ketika kita membandingkan titik 2D ke titik 3D yang setara, kita menjadi benar, tetapi ketika kita membalikkan perbandingan, kita menjadi salah (karena p2a gagal
instanceof Point3D
).Kesimpulan
Biasanya dimungkinkan untuk mengimplementasikan metode dalam subclass sedemikian rupa sehingga tidak lagi kompatibel dengan bagaimana kelas-super mengharapkannya bekerja.
Secara umum tidak mungkin untuk menerapkan equals () pada subclass yang sangat berbeda dengan cara yang kompatibel dengan kelas induknya.
Ketika Anda menulis sebuah kelas yang ingin Anda izinkan orang lain untuk membuat subkelas, itu ide yang sangat bagus untuk menulis kontrak untuk bagaimana masing-masing metode harus berperilaku. Yang lebih baik lagi adalah serangkaian unit test yang dapat dijalankan oleh orang-orang terhadap penerapan metode yang diganti untuk membuktikan bahwa mereka tidak melanggar kontrak. Hampir tidak ada yang melakukan itu karena terlalu banyak pekerjaan. Tetapi jika Anda peduli, itulah yang harus dilakukan.
Contoh bagus dari kontrak yang dieja dengan baik adalah Comparator . Abaikan saja apa yang dikatakannya
.equals()
karena alasan yang dijelaskan di atas. Berikut adalah contoh tentang bagaimana Pembanding dapat melakukan hal-hal yang.equals()
tidak bisa .Catatan
"Java Efektif" Item 8 dari Josh Bloch adalah sumber dari contoh ini, tetapi Bloch menggunakan ColorPoint yang menambahkan warna alih-alih sumbu ketiga dan menggunakan ganda sebagai ganti int. Contoh Java Bloch pada dasarnya digandakan oleh Odersky / Spoon / Venners yang membuat contoh mereka tersedia secara online.
Beberapa orang keberatan dengan contoh ini karena jika Anda membiarkan kelas induk tahu tentang sub-kelas, Anda dapat memperbaiki masalah ini. Itu benar jika ada sejumlah kecil sub-kelas dan jika orang tua tahu tentang mereka semua. Tetapi pertanyaan aslinya adalah tentang membuat API yang akan digunakan orang lain untuk membuat sub-kelas. Dalam hal ini, Anda biasanya tidak dapat memperbarui implementasi induk agar kompatibel dengan sub-kelas.
Bonus
Komparator juga menarik karena bekerja di sekitar masalah penerapan equals () dengan benar. Lebih baik lagi, ini mengikuti pola untuk memperbaiki masalah pewarisan jenis ini: pola desain Strategi. Typeclasses yang membuat orang Haskell dan Scala bersemangat juga merupakan pola Strategi. Warisan tidak buruk atau salah, itu hanya rumit. Untuk bacaan lebih lanjut, bacalah makalah Philip Wadler Bagaimana membuat polimorfisme ad-hoc menjadi lebih sedikit
sumber
equals
dari bagaimana Map dan Set mendefinisikannya. Kesetaraan sama sekali mengabaikan urutan, dengan efek yang, misalnya, dua SortedSet dengan elemen yang sama tetapi urutan berbeda masih sama.B
menjadi anak dari sebuah kelasA
dan jika Anda membuat instance objek dari sebuah kelasB
, Anda harus dapat melemparkanB
objek kelas ke orang tuanya dan menggunakan API variabel yang dicor tanpa kehilangan detail implementasi dari anak. Anda melanggar aturan dengan menyediakan properti ketiga. Bagaimana Anda berencana untuk mengaksesz
koordinat setelah Anda memberikanPoint3D
variabelPoint2D
, ketika kelas dasar tidak tahu properti seperti itu ada? Jika dengan melemparkan kelas anak ke basisnya Anda melanggar API publik, abstraksi Anda salah.Enkapsulasi Melemah Warisan
Saat Anda menerbitkan antarmuka dengan warisan yang diizinkan, Anda secara substansial meningkatkan ukuran antarmuka Anda. Setiap metode yang dapat ditimpa dapat diganti dan harus dianggap sebagai panggilan balik yang diberikan kepada konstruktor. Implementasi yang disediakan oleh kelas Anda hanyalah nilai default dari panggilan balik itu. Dengan demikian, beberapa jenis kontrak harus disediakan yang menunjukkan apa harapan pada metode tersebut. Ini jarang terjadi dan merupakan alasan utama mengapa kode berorientasi objek disebut brittle.
Di bawah ini adalah contoh nyata (disederhanakan) dari kerangka koleksi java, milik Peter Norvig ( http://norvig.com/java-iaq.html ).
Jadi apa yang terjadi jika kita mensubklasifikasikan ini?
Kami memiliki bug: Kadang-kadang kami menambahkan "dog" dan hashtable mendapatkan entri untuk "dogss". Penyebabnya adalah seseorang yang menyediakan implementasi put yang tidak diharapkan oleh orang yang mendesain kelas Hashtable.
Inheritance Breaks Extensibility
Jika Anda mengizinkan kelas Anda menjadi subkelas, Anda berkomitmen untuk tidak menambahkan metode apa pun ke kelas Anda. Ini bisa dilakukan tanpa merusak apa pun.
Saat Anda menambahkan metode baru ke antarmuka, siapa pun yang telah mewarisi dari kelas Anda perlu menerapkan metode itu.
sumber
Jika suatu metode dimaksudkan untuk dipanggil, Anda hanya perlu memastikan itu berfungsi dengan benar. Itu dia. Selesai
Jika suatu metode dirancang untuk diganti, Anda juga harus memikirkan dengan hati-hati tentang ruang lingkup metode ini: jika cakupannya terlalu besar, kelas anak sering kali perlu menyertakan kode copy-paste dari metode induk; jika terlalu kecil, banyak metode harus diganti untuk memiliki fungsi baru yang diinginkan - ini menambah kompleksitas dan jumlah baris yang tidak perlu.
Oleh karena itu pencipta metode induk perlu membuat asumsi tentang bagaimana kelas dan metodenya mungkin ditimpa di masa depan.
Namun, penulis berbicara tentang masalah berbeda dalam teks yang dikutip:
Pertimbangkan metode
a
yang biasanya dipanggil dari metodeb
, tetapi dalam beberapa kasus yang jarang dan tidak jelas dari metodec
. Jika penulis metode utama mengabaikanc
metode dan harapannyaa
, jelas bagaimana hal-hal bisa salah.Oleh karena itu lebih penting yang
a
didefinisikan secara jelas dan tidak ambigu, didokumentasikan dengan baik, "melakukan satu hal dan melakukannya dengan baik" - lebih daripada jika itu adalah metode yang hanya dirancang untuk dipanggil.sumber