Saya dapat melihat orang-orang bertanya sepanjang waktu apakah beberapa warisan harus dimasukkan ke dalam versi C # atau Java berikutnya. Orang C ++, yang cukup beruntung memiliki kemampuan ini, mengatakan bahwa ini seperti memberi seseorang tali untuk akhirnya menggantung diri.
Ada apa dengan warisan berganda? Apakah ada contoh beton?
oop
multiple-inheritance
language-theory
Vlad Gudim
sumber
sumber
Jawaban:
Masalah paling jelas adalah dengan fungsi overriding.
Katakanlah memiliki dua kelas
A
danB
keduanya mendefinisikan metodedoSomething
. Sekarang Anda mendefinisikan kelas ketigaC
, yang mewarisi dariA
danB
, tetapi Anda tidak menggantidoSomething
metode.Ketika kompilator memasukkan kode ini ...
... implementasi metode mana yang harus digunakan? Tanpa klarifikasi lebih lanjut, compiler tidak mungkin dapat menyelesaikan ambiguitas tersebut.
Selain menimpa, masalah besar lainnya dengan multiple inheritance adalah tata letak objek fisik dalam memori.
Bahasa seperti C ++ dan Java dan C # membuat tata letak berbasis alamat tetap untuk setiap jenis objek. Sesuatu seperti ini:
Saat compiler menghasilkan kode mesin (atau bytecode), ia menggunakan offset numerik tersebut untuk mengakses setiap metode atau kolom.
Warisan ganda membuatnya sangat rumit.
Jika kelas
C
mewarisi dariA
danB
, kompilator harus memutuskan apakah akan menyusun data secaraAB
berurutan atauBA
berurutan.Tapi sekarang bayangkan Anda memanggil metode pada suatu
B
objek. Apakah ini benar-benar hanya aB
? Atau apakah itu sebenarnya sebuahC
objek yang disebut secara polimorfis, melaluiB
antarmukanya? Bergantung pada identitas objek yang sebenarnya, tata letak fisik akan berbeda, dan tidak mungkin mengetahui offset fungsi yang akan dipanggil di situs panggilan.Cara menangani sistem semacam ini adalah dengan membuang pendekatan tata letak tetap, yang memungkinkan setiap objek ditanyai tata letaknya sebelum mencoba memanggil fungsi atau mengakses bidangnya.
Jadi ... singkat cerita ... sulit bagi penulis kompiler untuk mendukung banyak warisan. Jadi ketika seseorang seperti Guido van Rossum mendesain python, atau ketika Anders Hejlsberg mendesain c #, mereka tahu bahwa mendukung multiple inheritance akan membuat implementasi compiler jauh lebih kompleks, dan mungkin mereka tidak berpikir bahwa keuntungannya sepadan dengan biayanya.
sumber
Masalah yang kalian sebutkan sebenarnya tidak terlalu sulit untuk dipecahkan. Faktanya, misalnya Eiffel melakukannya dengan sangat baik! (dan tanpa memberikan pilihan sewenang-wenang atau apa pun)
Misalnya jika Anda mewarisi dari A dan B, keduanya memiliki metode foo (), maka tentu saja Anda tidak ingin pilihan sewenang-wenang di kelas C Anda yang mewarisi dari A dan B. Anda harus mendefinisikan ulang foo sehingga jelas apa yang akan terjadi. digunakan jika c.foo () dipanggil atau jika tidak, Anda harus mengganti nama salah satu metode di C. (bisa menjadi bar ())
Saya juga berpikir bahwa warisan berganda seringkali cukup berguna. Jika Anda melihat perpustakaan Eiffel, Anda akan melihat bahwa itu digunakan di semua tempat dan secara pribadi saya melewatkan fitur itu ketika saya harus kembali ke pemrograman di Java.
sumber
Masalah berlian :
sumber
someZ
dan ingin melemparkannya keObject
dan kemudian keB
? Yang mana yangB
akan didapatnya?Object
dan kembali ke jenis itu ...Warisan berganda adalah salah satu dari hal-hal yang tidak sering digunakan, dan dapat disalahgunakan, tetapi terkadang diperlukan.
Saya tidak pernah mengerti tidak menambahkan fitur, hanya karena itu mungkin disalahgunakan, ketika tidak ada alternatif yang baik. Antarmuka bukanlah alternatif untuk multiple inheritance. Pertama, mereka tidak mengizinkan Anda menerapkan prasyarat atau prasyarat. Sama seperti alat lainnya, Anda perlu tahu kapan alat itu cocok untuk digunakan, dan bagaimana cara menggunakannya.
sumber
assert
?katakanlah Anda memiliki objek A dan B yang keduanya diwarisi oleh C. A dan B keduanya mengimplementasikan foo () dan C tidak. Saya sebut C.foo (). Implementasi mana yang dipilih? Ada masalah lain, tetapi hal seperti ini adalah masalah besar.
sumber
Masalah utama dengan beberapa warisan diringkas dengan baik dengan contoh tloach. Saat mewarisi dari beberapa kelas dasar yang mengimplementasikan fungsi atau bidang yang sama, kompilator harus membuat keputusan tentang implementasi apa yang akan diwarisi.
Ini menjadi lebih buruk ketika Anda mewarisi dari beberapa kelas yang mewarisi dari kelas dasar yang sama. (warisan berlian, jika Anda menggambar pohon warisan, Anda mendapatkan bentuk berlian)
Masalah-masalah ini tidak terlalu bermasalah untuk diatasi oleh kompiler. Tetapi pilihan yang harus dibuat oleh compiler di sini agak sewenang-wenang, ini membuat kode menjadi kurang intuitif.
Saya menemukan bahwa ketika melakukan desain OO yang baik, saya tidak pernah membutuhkan multiple inheritance. Dalam kasus saya benar-benar membutuhkannya, saya biasanya menemukan saya telah menggunakan warisan untuk menggunakan kembali fungsionalitas sementara warisan hanya sesuai untuk hubungan "is-a".
Ada teknik lain seperti mixin yang memecahkan masalah yang sama dan tidak memiliki masalah yang dimiliki beberapa pewarisan.
sumber
([..bool..]? "test": 1)
?Saya tidak berpikir masalah berlian adalah masalah, saya akan menganggap itu menyesatkan, tidak ada yang lain.
Masalah terburuk, dari sudut pandang saya, dengan multiple inheritance adalah RAD - korban dan orang-orang yang mengaku sebagai pengembang tetapi pada kenyataannya terjebak dengan setengah - pengetahuan (paling banter).
Secara pribadi, saya akan sangat senang jika saya akhirnya dapat melakukan sesuatu di Windows Forms seperti ini (ini bukan kode yang benar, tetapi itu akan memberi Anda ide):
Ini adalah masalah utama yang saya alami karena tidak memiliki banyak warisan. Anda BISA melakukan sesuatu yang mirip dengan antarmuka, tetapi ada yang saya sebut "kode ***", ini adalah c *** berulang yang menyakitkan yang harus Anda tulis di setiap kelas untuk mendapatkan konteks data, misalnya.
Menurut pendapat saya, sama sekali tidak perlu, tidak sedikit pun, untuk pengulangan kode APAPUN dalam bahasa modern.
sumber
Common Lisp Object System (CLOS) adalah contoh lain dari sesuatu yang mendukung MI sambil menghindari masalah gaya C ++: pewarisan diberikan default yang masuk akal , sambil tetap memungkinkan Anda kebebasan untuk secara eksplisit memutuskan bagaimana tepatnya, katakanlah, memanggil perilaku super .
sumber
Tidak ada yang salah dalam multiple inheritance itu sendiri. Masalahnya adalah menambahkan beberapa pewarisan ke bahasa yang tidak dirancang dengan banyak pewarisan sejak awal.
Bahasa Eiffel mendukung banyak pewarisan tanpa batasan dengan cara yang sangat efisien dan produktif, tetapi bahasa itu dirancang sejak awal untuk mendukungnya.
Fitur ini kompleks untuk diterapkan pada pengembang kompilator, tetapi tampaknya kelemahan itu dapat dikompensasi oleh fakta bahwa dukungan multiple inheritance yang baik dapat menghindari dukungan fitur lain (yaitu tidak perlu Antarmuka atau Metode Ekstensi).
Saya pikir mendukung multiple inheritance atau tidak lebih merupakan masalah pilihan, masalah prioritas. Fitur yang lebih kompleks membutuhkan lebih banyak waktu untuk diterapkan dan beroperasi dengan benar dan mungkin lebih kontroversial. Implementasi C ++ mungkin menjadi alasan mengapa multiple inheritance tidak diimplementasikan di C # dan Java ...
sumber
Salah satu tujuan desain kerangka kerja seperti Java dan .NET adalah memungkinkan kode yang dikompilasi untuk bekerja dengan satu versi pustaka yang telah dikompilasi sebelumnya, untuk bekerja sama baiknya dengan versi pustaka berikutnya, bahkan jika versi berikutnya tambahkan fitur baru. Sementara paradigma normal dalam bahasa seperti C atau C ++ adalah mendistribusikan executable yang ditautkan secara statis yang berisi semua pustaka yang mereka butuhkan, paradigma dalam .NET dan Java adalah mendistribusikan aplikasi sebagai kumpulan komponen yang "ditautkan" pada waktu proses .
Model COM yang mendahului .NET mencoba menggunakan pendekatan umum ini, tetapi tidak benar-benar memiliki warisan - sebagai gantinya, setiap definisi kelas secara efektif mendefinisikan kelas dan antarmuka dengan nama yang sama yang berisi semua anggota publiknya. Contoh adalah tipe kelas, sedangkan referensi adalah tipe antarmuka. Mendeklarasikan kelas sebagai turunan dari kelas lain sama dengan mendeklarasikan kelas sebagai mengimplementasikan antarmuka lain, dan mengharuskan kelas baru untuk mengimplementasikan ulang semua anggota publik dari kelas yang diturunkan. Jika Y dan Z diturunkan dari X, lalu W berasal dari Y dan Z, tidak masalah jika Y dan Z mengimplementasikan anggota X secara berbeda, karena Z tidak akan dapat menggunakan implementasinya - itu harus menentukan sendiri. W mungkin merangkum instance Y dan / atau Z,
Kesulitan di Java dan .NET adalah bahwa kode diizinkan untuk mewarisi anggota dan memiliki akses ke mereka secara implisit merujuk ke anggota induk. Misalkan seseorang memiliki kelas WZ terkait seperti di atas:
Sepertinya
W.Test()
harus membuat instance panggilan W implementasi metode virtual yangFoo
ditentukan dalamX
. Misalkan, bagaimanapun, bahwa Y dan Z sebenarnya berada dalam modul yang dikompilasi secara terpisah, dan meskipun mereka didefinisikan seperti di atas ketika X dan W dikompilasi, mereka kemudian diubah dan dikompilasi ulang:Sekarang apa yang seharusnya menjadi efek dari panggilan
W.Test()
? Jika program harus ditautkan secara statis sebelum didistribusikan, tahap tautan statis mungkin dapat membedakan bahwa meskipun program tidak memiliki ambiguitas sebelum Y dan Z diubah, perubahan ke Y dan Z telah membuat hal-hal menjadi ambigu dan penaut dapat menolak untuk membangun program kecuali atau sampai ambiguitas tersebut diselesaikan. Di sisi lain, mungkin saja orang yang memiliki W dan versi baru Y dan Z adalah seseorang yang hanya ingin menjalankan program dan tidak memiliki kode sumber untuk semua itu. SaatW.Test()
berlari, tidak akan jelas lagi apaW.Test()
harus dilakukan, tetapi hingga pengguna mencoba menjalankan W dengan versi baru Y dan Z, tidak mungkin ada bagian dari sistem yang dapat mengenali adanya masalah (kecuali W dianggap tidak sah bahkan sebelum perubahan ke Y dan Z) .sumber
Berlian tidak menjadi masalah, selama Anda tidak menggunakan apa pun seperti warisan virtual C ++: dalam warisan normal setiap kelas dasar menyerupai bidang anggota (sebenarnya mereka ditata dalam RAM dengan cara ini), memberi Anda gula sintaksis dan kemampuan ekstra untuk mengganti metode virtual lainnya. Itu mungkin menimbulkan beberapa ambiguitas pada waktu kompilasi tetapi biasanya mudah dipecahkan.
Di sisi lain, dengan warisan virtual itu terlalu mudah lepas kendali (dan kemudian menjadi berantakan). Pertimbangkan sebagai contoh diagram "hati":
Di C ++ sama sekali tidak mungkin: segera setelah
F
danG
digabungkan menjadi satu kelas,A
s mereka juga digabungkan, titik. Itu berarti Anda mungkin tidak pernah mempertimbangkan kelas dasar Buram di C ++ (dalam contoh ini Anda harus membangunA
diH
sehingga Anda harus tahu bahwa itu suatu tempat dalam hirarki). Namun, dalam bahasa lain ini mungkin berhasil; misalnya,F
danG
secara eksplisit dapat mendeklarasikan A sebagai "internal", sehingga melarang penggabungan konsekuen dan secara efektif membuat diri mereka solid.Contoh menarik lainnya ( bukan khusus C ++):
Di sini, hanya
B
menggunakan warisan virtual. JadiE
berisi duaB
yang berbagi samaA
. Dengan cara ini, Anda bisa mendapatkanA*
pointer yang menunjuk keE
, tetapi Anda tidak bisa dilemparkan ke sebuahB*
pointer meskipun objek ini sebenarnyaB
sebagai pemeran tersebut ambigu, dan ambiguitas ini tidak dapat terdeteksi pada waktu kompilasi (kecuali compiler melihat seluruh program). Ini kode tesnya:Selain itu, implementasinya mungkin sangat kompleks (bergantung pada bahasa; lihat jawaban benjismith).
sumber