Argumen "mengerikan untuk ingatan" sepenuhnya salah, tetapi ini merupakan "praktik buruk" secara objektif . Ketika Anda mewarisi dari kelas, Anda tidak hanya mewarisi bidang dan metode yang Anda minati. Sebaliknya, Anda mewarisi segalanya . Setiap metode yang dinyatakannya, meskipun tidak berguna bagi Anda. Dan yang paling penting, Anda juga mewarisi semua kontrak dan jaminan yang disediakan kelas tersebut.
SOLID singkatan menyediakan beberapa heuristik untuk desain berorientasi objek yang baik. Di sini, saya nterface Pemisahan Prinsip (ISP) dan L iskov Pergantian Pricinple (LSP) memiliki sesuatu untuk dikatakan.
ISP memberi tahu kami untuk menjaga antarmuka kami sekecil mungkin. Tetapi dengan mewarisi dari ArrayList
, Anda mendapatkan banyak, banyak metode. Apakah itu berarti untuk get()
, remove()
, set()
(ganti), atau add()
(insert) node anak pada indeks tertentu? Apakah masuk akal untuk ensureCapacity()
daftar yang mendasarinya? Apa artinya sort()
sebuah Node? Apakah pengguna kelas Anda benar-benar seharusnya mendapatkan subList()
? Karena Anda tidak dapat menyembunyikan metode yang tidak Anda inginkan, satu-satunya solusi adalah menjadikan ArrayList sebagai variabel anggota, dan meneruskan semua metode yang sebenarnya Anda inginkan:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Jika Anda benar-benar menginginkan semua metode yang Anda lihat dalam dokumentasi, kami dapat beralih ke LSP. LSP memberi tahu kita bahwa kita harus dapat menggunakan subclass di mana pun kelas induknya diharapkan. Jika suatu fungsi mengambil ArrayList
parameter sebagai dan kami meneruskannya Node
, tidak ada yang seharusnya berubah.
Kompatibilitas subclass dimulai dengan hal-hal sederhana seperti jenis tanda tangan. Saat Anda mengganti metode, Anda tidak bisa membuat tipe parameter lebih ketat karena itu mungkin mengecualikan penggunaan yang sah dengan kelas induk. Tapi itu adalah sesuatu yang diperiksa kompiler untuk kita di Jawa.
Tetapi LSP berjalan jauh lebih dalam: Kami harus menjaga kompatibilitas dengan semua yang dijanjikan oleh dokumentasi semua kelas dan antarmuka induk. Dalam jawaban mereka , Lynn telah menemukan satu kasus di mana List
antarmuka (yang telah Anda warisi melalui ArrayList
) menjamin cara equals()
dan hashCode()
metode yang seharusnya bekerja. Untuk hashCode()
Anda bahkan diberikan algoritma tertentu yang harus diimplementasikan dengan tepat. Anggap Anda telah menulis ini Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Ini mensyaratkan bahwa value
tidak dapat berkontribusi pada hashCode()
dan tidak dapat mempengaruhi equals()
. The List
antarmuka - yang Anda berjanji untuk menghormati dengan mewarisi dari itu - membutuhkan new Node(0).equals(new Node(123))
untuk menjadi kenyataan.
Karena mewarisi dari kelas membuatnya terlalu mudah untuk secara tidak sengaja mengingkari janji yang dibuat oleh kelas induk, dan karena biasanya mengekspos lebih banyak metode daripada yang Anda maksud, umumnya disarankan agar Anda lebih memilih komposisi daripada warisan . Jika Anda harus mewarisi sesuatu, disarankan untuk hanya mewarisi antarmuka. Jika Anda ingin menggunakan kembali perilaku kelas tertentu, Anda bisa menjadikannya sebagai objek terpisah dalam variabel instan, dengan cara itu semua janji dan persyaratannya tidak dipaksakan pada Anda.
Terkadang, bahasa alami kita menunjukkan hubungan pewarisan: Mobil adalah kendaraan. Sepeda motor adalah kendaraan. Haruskah saya mendefinisikan kelas Car
dan Motorcycle
mewarisi dari Vehicle
kelas? Desain berorientasi objek bukan tentang mirroring dunia nyata persis dalam kode kita. Kita tidak dapat dengan mudah menyandikan taksonomi kaya dari dunia nyata dalam kode sumber kita.
Salah satu contohnya adalah masalah pemodelan bos-karyawan. Kami memiliki beberapa Person
s, masing-masing dengan nama dan alamat. An Employee
adalah Person
dan memiliki a Boss
. A Boss
juga a Person
. Jadi haruskah saya membuat Person
kelas yang diwarisi oleh Boss
dan Employee
? Sekarang saya memiliki masalah: bos juga seorang karyawan dan memiliki atasan lain. Jadi sepertinya Boss
harus diperluas Employee
. Tapi CompanyOwner
itu Boss
tapi bukan Employee
? Apa pun jenis grafik warisan entah bagaimana akan rusak di sini.
OOP bukan tentang hierarki, pewarisan, dan penggunaan kembali kelas yang ada, ini tentang generalisasi perilaku . OOP adalah tentang “Saya memiliki banyak objek dan ingin mereka melakukan hal tertentu - dan saya tidak peduli bagaimana caranya.” Itulah gunanya antarmuka . Jika Anda mengimplementasikan Iterable
antarmuka untuk Anda Node
karena Anda ingin membuatnya dapat diubah, itu tidak masalah. Jika Anda mengimplementasikan Collection
antarmuka karena Anda ingin menambah / menghapus node anak dll, maka itu tidak masalah. Tetapi mewarisi dari kelas lain karena kebetulan memberi Anda semua yang tidak, atau setidaknya tidak kecuali Anda telah memikirkannya dengan seksama seperti diuraikan di atas.