Mengapa menghindari Warisan Java “Perluas”

40

Kata Jame Gosling

"Anda harus menghindari warisan implementasi sedapat mungkin."

dan sebagai gantinya, gunakan pewarisan antarmuka.

Tapi kenapa? Bagaimana kita dapat menghindari mewarisi struktur suatu objek menggunakan kata kunci "extends", dan pada saat yang sama membuat kode kita Berorientasi Objek?

Bisakah seseorang tolong berikan contoh Berorientasi Objek yang menggambarkan konsep ini dalam skenario seperti "memesan buku di toko buku?"

pemula
sumber
4
duplikat dari programmers.stackexchange.com/questions/65179/…
Steven A. Lowe
Masalahnya adalah Anda hanya dapat memperpanjang satu kelas.

Jawaban:

38

Gosling tidak mengatakan untuk menghindari penggunaan kata kunci extends ... dia hanya mengatakan untuk tidak menggunakan pewarisan sebagai sarana untuk mencapai penggunaan kembali kode.

Warisan tidaklah buruk dan merupakan alat (esensial) yang sangat kuat untuk digunakan dalam menciptakan struktur OO. Namun ketika tidak digunakan dengan benar (yaitu ketika digunakan untuk sesuatu yang lain selain membuat struktur Obyek) itu menciptakan kode yang sangat erat digabungkan dan sangat sulit untuk dipelihara.

Karena pewarisan harus digunakan untuk membuat struktur polimorfik, itu dapat dilakukan dengan mudah dengan antarmuka, oleh karena itu pernyataan untuk menggunakan antarmuka bukan kelas ketika merancang Struktur Objek Anda. Jika Anda menemukan diri Anda menggunakan extends sebagai cara untuk menghindari kode copy-paste dari satu objek ke objek lain mungkin Anda menggunakannya salah dan akan lebih baik disajikan dengan kelas khusus untuk menangani fungsi ini dan membagikan kode itu melalui komposisi sebagai gantinya.

Baca tentang Prinsip Substitusi Liskov dan pahami. Setiap kali Anda akan memperpanjang kelas (atau antarmuka untuk hal itu) tanyakan pada diri sendiri apakah Anda melanggar prinsip, jika ya maka Anda kemungkinan besar (ab) menggunakan warisan dengan cara yang tidak wajar. Ini mudah dilakukan dan seringkali tampak seperti hal yang benar tetapi akan membuat kode Anda lebih sulit untuk dipertahankan, diuji, dan dikembangkan.

--- Sunting Jawaban ini membuat argumen yang cukup bagus untuk menjawab pertanyaan secara lebih umum.

Newtopian
sumber
3
Warisan (implementasi) tidak penting untuk membuat struktur OO. Anda dapat memiliki bahasa OO tanpa itu.
Andres F.
Bisakah Anda memberikan contoh? Saya tidak pernah melihat OO tanpa warisan.
Newtopian
1
Masalahnya adalah bahwa tidak ada definisi yang diterima tentang apa itu OOP. (Sebagai tambahan, bahkan Alan Kay menyesali interpretasi umum dari definisinya, dengan penekanan saat ini pada "objek" bukan "pesan"). Ini adalah upaya untuk menjawab pertanyaan Anda . Saya setuju bahwa jarang melihat OOP tanpa warisan; maka argumen saya hanyalah bahwa itu tidak penting ;)
Andres F.
9

Pada hari-hari awal pemrograman berorientasi objek, warisan implementasi adalah palu emas penggunaan kembali kode. Jika ada bagian fungsionalitas di beberapa kelas yang Anda butuhkan, hal yang harus dilakukan adalah mewarisi secara terbuka dari kelas itu (ini adalah hari-hari ketika C ++ lebih lazim daripada Java), dan Anda mendapatkan fungsionalitas itu "gratis."

Kecuali, itu tidak benar-benar gratis. Hasilnya adalah hierarki warisan yang sangat berbelit-belit yang hampir tidak mungkin untuk dipahami, termasuk pohon warisan berbentuk berlian yang sulit untuk ditangani. Ini adalah bagian dari alasan mengapa para perancang Java memilih untuk tidak mendukung multiple inheritance of class.

Nasihat Gosling (dan pernyataan lainnya) seperti itu adalah obat kuat untuk penyakit yang cukup serius, dan mungkin beberapa bayi dikeluarkan dengan air mandi. Tidak ada yang salah dengan menggunakan warisan untuk memodelkan hubungan antara kelas yang sebenarnya is-a. Tetapi jika Anda hanya ingin menggunakan fungsionalitas dari kelas lain, Anda akan lebih baik menyelidiki opsi lain terlebih dahulu.

JohnMcG
sumber
6

Warisan telah mengumpulkan reputasi yang cukup menakutkan belakangan ini. Salah satu alasan untuk menghindarinya sejalan dengan prinsip desain "komposisi lebih dari warisan", yang pada dasarnya mendorong orang untuk tidak menggunakan warisan di mana itu bukan hubungan yang sebenarnya, hanya untuk menghemat beberapa penulisan kode. Warisan semacam ini dapat menyebabkan beberapa sulit ditemukan dan sulit untuk memperbaiki bug. Alasan teman Anda mungkin menyarankan untuk menggunakan antarmuka sebagai gantinya adalah karena antarmuka memberikan solusi yang lebih fleksibel dan dapat dipelihara untuk api yang didistribusikan. Mereka lebih sering memungkinkan perubahan dilakukan ke api tanpa melanggar kode pengguna, di mana kelas yang terbuka sering memecahkan kode pengguna. Contoh terbaik dari ini dalam. Net adalah perbedaan penggunaan antara Daftar dan IList.

Morgan Herlocker
sumber
5

Karena Anda hanya dapat memperluas satu kelas, sementara Anda dapat mengimplementasikan banyak antarmuka.

Saya pernah mendengar bahwa warisan menentukan siapa Anda, tetapi antarmuka menentukan peran yang dapat Anda mainkan.

Antarmuka juga memungkinkan penggunaan polimorfisme yang lebih baik.

Itu mungkin bagian dari alasannya.

Mahmoud Hossam
sumber
mengapa downvote?
Mahmoud Hossam
6
Jawabannya menempatkan kereta di depan kuda. Java hanya memungkinkan Anda memperluas satu kelas karena prinsip dasar Gosling (perancang Jawa) diartikulasikan. Ini seperti mengatakan anak-anak harus mengenakan helm saat mengendarai sepeda karena itulah hukumnya. Itu benar, tetapi kedua hukum dan saran berasal dari penghindaran cedera kepala.
JohnMcG
@JohnMcG Saya tidak menjelaskan pilihan desain yang dibuat Gosling, saya hanya memberi saran kepada seorang pembuat kode, para pembuat kode biasanya tidak peduli dengan desain bahasa.
Mahmoud Hossam
1

Saya telah diajarkan ini juga dan saya lebih suka antarmuka jika memungkinkan (tentu saja saya masih menggunakan warisan mana yang masuk akal).

Satu hal yang saya pikirkan adalah memisahkan kode Anda dari implementasi spesifik. Katakanlah saya memiliki kelas yang disebut ConsoleWriter dan itu akan diteruskan ke metode untuk menulis sesuatu dan menulisnya ke konsol. Sekarang katakanlah saya ingin beralih ke pencetakan ke jendela GUI. Nah sekarang saya harus memodifikasi metode atau menulis yang baru yang menggunakan GUIWriter sebagai parameter. Jika saya mulai dengan mendefinisikan antarmuka IWriter dan memiliki metode mengambil IWriter saya bisa mulai dengan ConsoleWriter (yang akan mengimplementasikan antarmuka IWriter) dan kemudian menulis kelas baru yang disebut GUIWriter (yang juga mengimplementasikan antarmuka IWriter) dan kemudian saya hanya perlu mengganti kelas yang sedang lewat.

Hal lain (yang berlaku untuk C #, tidak yakin tentang Java) adalah Anda hanya dapat memperluas 1 kelas tetapi mengimplementasikan banyak antarmuka. Katakanlah saya punya kelas bernama Teacher, MathTeacher, dan HistoryTeacher. Sekarang MathTeacher dan HistoryTeacher meluas dari Guru tetapi bagaimana jika kita menginginkan kelas yang mewakili seseorang yang merupakan seorang MathTeacher dan HistoryTeacher. Ini bisa menjadi sangat berantakan ketika mencoba mewarisi dari beberapa kelas ketika Anda hanya bisa melakukannya satu per satu (ada cara tetapi mereka tidak sepenuhnya optimal). Dengan antarmuka, Anda dapat memiliki 2 antarmuka yang disebut IMathTeacher dan IHistoryTeacher dan kemudian memiliki satu kelas yang meluas dari Guru dan mengimplementasikan 2 antarmuka tersebut.

Satu kelemahan yang dengan menggunakan antarmuka adalah bahwa kadang-kadang saya melihat orang menggandakan kode (karena Anda harus membuat implementasi untuk setiap kelas mengimplementasikan antarmuka) namun ada solusi bersih untuk masalah ini, misalnya penggunaan hal-hal seperti delegasi (tidak yakin apa yang setara dengan Java itu).

Pemikiran alasan terbesar untuk menggunakan antarmuka atas warisan adalah decoupling dari kode implementasi tetapi tidak berpikir bahwa warisan itu jahat karena masih sangat berguna.

ryanzec
sumber
2
"Anda hanya dapat memperluas 1 kelas tetapi mengimplementasikan banyak antarmuka" Itu berlaku untuk Java dan juga C #.
FrustratedWithFormsDesigner
Salah satu solusi yang mungkin untuk masalah duplikasi kode adalah membuat kelas abstrak yang mengimplementasikan antarmuka dan memperluasnya. Gunakan dengan hati-hati; hanya ada beberapa kasus yang saya lihat masuk akal.
Michael K
0

Jika Anda mewarisi dari suatu kelas, Anda mengambil ketergantungan tidak hanya pada kelas itu tetapi Anda juga harus menghormati apa pun kontrak implisit yang telah dibuat oleh klien dari kelas itu. Paman Bob memberikan contoh yang bagus tentang bagaimana mudah untuk secara halus melanggar Prinsip Substitusi Liskov menggunakan Square dasar memperluas dilema Rectangle . Antarmuka, karena mereka tidak memiliki implementasi juga tidak memiliki kontrak tersembunyi.

Michael Brown
sumber
2
Satu hal menarik: masalah Circle-ellipse akan tetap terjadi bahkan jika hanya antarmuka murni yang digunakan, jika objek harus dapat diubah.
rwong