Di Jawa tidak ada virtual
, new
, override
kata kunci untuk definisi metode. Jadi cara kerja suatu metode mudah dimengerti. Karena jika DerivedClass memperluas BaseClass dan memiliki metode dengan nama yang sama dan tanda tangan yang sama dari BaseClass maka penimpaan akan terjadi pada polimorfisme run-time (asalkan metode ini tidak static
).
BaseClass bcdc = new DerivedClass();
bcdc.doSomething() // will invoke DerivedClass's doSomething method.
Sekarang datang ke C # akan ada begitu banyak kebingungan dan sulit untuk memahami bagaimana new
atau virtual+derive
atau override virtual + baru bekerja.
Saya tidak dapat memahami mengapa di dunia ini saya akan menambahkan metode pada saya DerivedClass
dengan nama dan tanda tangan yang sama dengan BaseClass
dan mendefinisikan perilaku baru tetapi pada polimorfisme run-time, BaseClass
metode ini akan dipanggil! (yang tidak mengesampingkan tetapi secara logis seharusnya).
Jika virtual + override
implementasi logisnya benar, tetapi programmer harus memikirkan metode mana yang harus ia beri izin kepada pengguna untuk diganti pada saat pengkodean. Yang memiliki beberapa pro-con (jangan pergi ke sana sekarang).
Jadi mengapa di C # ada begitu banyak ruang untuk alasan dan kebingungan yang tidak logis. Jadi bisakah saya membingkai ulang pertanyaan saya sebagai konteks dunia nyata yang harus saya pikirkan tentang penggunaan virtual + override
alih-alih new
dan juga penggunaan new
alih-alih virtual + override
?
Setelah beberapa jawaban yang sangat baik terutama dari Omar , saya mendapatkan bahwa desainer C # lebih menekankan pada programmer harus berpikir sebelum mereka membuat metode, yang bagus dan menangani beberapa kesalahan pemula dari Jawa.
Sekarang saya punya pertanyaan. Seperti di Jawa jika saya punya kode suka
Vehicle vehicle = new Car();
vehicle.accelerate();
dan kemudian saya membuat kelas baru yang SpaceShip
berasal dari Vehicle
. Lalu saya ingin mengubah semua car
menjadi SpaceShip
objek saya hanya perlu mengubah satu baris kode
Vehicle vehicle = new SpaceShip();
vehicle.accelerate();
Ini tidak akan merusak logika saya di titik kode mana pun.
Tetapi dalam kasus C # jika SpaceShip
tidak menimpa Vehicle
kelas ' accelerate
dan gunakan new
maka logika kode saya akan rusak. Bukankah itu merugikan?
sumber
final
; itu justru sebaliknya).@Override
.Jawaban:
Karena Anda bertanya mengapa C # melakukannya dengan cara ini, yang terbaik adalah bertanya kepada pembuat C #. Anders Hejlsberg, arsitek utama untuk C #, menjawab mengapa mereka memilih untuk tidak menggunakan virtual secara default (seperti di Jawa) dalam sebuah wawancara , cuplikan terkait di bawah ini.
Perlu diingat bahwa Java memiliki virtual secara default dengan kata kunci terakhir untuk menandai metode sebagai non-virtual. Masih dua konsep untuk dipelajari, tetapi banyak orang tidak tahu tentang kata kunci akhir atau tidak menggunakan secara proaktif. C # memaksa seseorang untuk menggunakan virtual dan baru / menimpa untuk secara sadar membuat keputusan tersebut.
Wawancara ini memiliki lebih banyak diskusi tentang bagaimana pengembang berpikir tentang desain warisan kelas, dan bagaimana hal itu menyebabkan keputusan mereka.
Sekarang untuk pertanyaan berikut:
Ini akan terjadi ketika kelas turunan ingin menyatakan bahwa ia tidak mematuhi kontrak kelas dasar, tetapi memiliki metode dengan nama yang sama. (Untuk siapa saja yang tidak tahu perbedaan antara
new
danoverride
di C #, lihat halaman MSDN ini ).Skenario yang sangat praktis adalah ini:
Vehicle
.Vehicle
.Vehicle
Kelas Anda tidak memiliki metode apa punPerformEngineCheck()
.Car
kelas saya , saya menambahkan metodePerformEngineCheck()
.PerformEngineCheck()
.Jadi ketika saya mengkompilasi ulang terhadap API baru Anda, C # memperingatkan saya tentang masalah ini, misalnya
Jika basis
PerformEngineCheck()
tidakvirtual
:Dan jika basisnya
PerformEngineCheck()
adalahvirtual
:Sekarang, saya harus secara eksplisit membuat keputusan apakah kelas saya benar-benar memperpanjang kontrak kelas dasar, atau jika itu adalah kontrak yang berbeda tetapi kebetulan memiliki nama yang sama.
new
, saya tidak merusak klien saya jika fungsionalitas metode dasar berbeda dari metode turunan. Kode apa pun yang dirujukVehicle
tidak akanCar.PerformEngineCheck()
dipanggil, tetapi kode yang memiliki referensiCar
akan terus melihat fungsi yang sama dengan yang saya tawarkanPerformEngineCheck()
.Contoh serupa adalah ketika metode lain di kelas dasar mungkin memanggil
PerformEngineCheck()
(terutama dalam versi yang lebih baru), bagaimana seseorang mencegahnya memanggilPerformEngineCheck()
kelas turunan? Di Jawa, keputusan itu ada di tangan kelas dasar, tetapi tidak tahu apa-apa tentang kelas turunannya. Dalam C #, keputusan itu terletak baik pada kelas dasar (melaluivirtual
kata kunci), dan pada kelas turunan (melaluinew
danoverride
kata kunci).Tentu saja, kesalahan yang dilemparkan oleh kompiler juga menyediakan alat yang bermanfaat bagi programmer untuk tidak secara tiba-tiba membuat kesalahan (mis. Menimpa atau menyediakan fungsionalitas baru tanpa disadari.)
Seperti yang dikatakan Anders, dunia nyata memaksa kita ke dalam masalah-masalah semacam itu yang, jika kita memulai dari awal, kita tidak akan pernah mau masuk ke dalamnya.
EDIT: Menambahkan contoh di mana
new
harus digunakan untuk memastikan kompatibilitas antarmuka.EDIT: Ketika sedang membaca komentar, saya juga menemukan tulisan oleh Eric Lippert (saat itu salah satu anggota komite desain C #) pada contoh skenario lainnya (disebutkan oleh Brian).
BAGIAN 2: Berdasarkan pertanyaan yang diperbarui
Siapa yang memutuskan apakah
SpaceShip
benar-benar mengesampingkanVehicle.accelerate()
atau jika itu berbeda? Itu harus menjadiSpaceShip
pengembang. Jadi jikaSpaceShip
pengembang memutuskan bahwa mereka tidak menjaga kontrak kelas dasar, maka panggilan Anda untukVehicle.accelerate()
tidak boleh pergi keSpaceShip.accelerate()
, atau haruskah itu? Saat itulah mereka akan menandainya sebagainew
. Namun, jika mereka memutuskan bahwa itu memang mempertahankan kontrak, maka mereka sebenarnya akan menandainyaoverride
. Dalam kedua kasus, kode Anda akan berperilaku benar dengan memanggil metode yang benar berdasarkan kontrak . Bagaimana kode Anda memutuskan apakahSpaceShip.accelerate()
benar-benar mengesampingkanVehicle.accelerate()
atau jika itu adalah tabrakan nama? (Lihat contoh saya di atas).Namun, dalam kasus warisan implisit, bahkan jika
SpaceShip.accelerate()
tidak menjaga kontrak dariVehicle.accelerate()
, metode panggilan akan tetap pergi keSpaceShip.accelerate()
.sumber
Itu dilakukan karena itu hal yang benar untuk dilakukan. Faktanya adalah bahwa membiarkan semua metode ditimpa adalah salah; itu mengarah ke masalah kelas dasar yang rapuh, di mana Anda tidak memiliki cara untuk mengatakan apakah perubahan ke kelas dasar akan merusak subclass. Karena itu Anda harus memasukkan metode yang tidak boleh diganti atau memasukkan daftar putih metode yang diizinkan diganti. Dari keduanya, daftar putih tidak hanya lebih aman (karena Anda tidak dapat membuat kelas dasar yang rapuh secara tidak sengaja ), itu juga membutuhkan lebih sedikit pekerjaan karena Anda harus menghindari pewarisan yang mendukung komposisi.
sumber
final
modifier, jadi apa masalahnya?HashMap
?). Entah desain untuk warisan dan membuat kontrak jelas atau tidak dan memastikan tidak ada yang bisa mewarisi kelas.@Override
ditambahkan sebagai bandaid karena sudah terlambat untuk mengubah perilaku pada saat itu, tetapi bahkan devs Jawa asli (khususnya Josh Bloch) setuju bahwa ini adalah keputusan yang buruk.Seperti yang dikatakan Robert Harvey, itu semua sesuai dengan yang biasa Anda lakukan. Saya menemukan kurangnya fleksibilitas Java ini aneh.
Yang mengatakan, mengapa ini di tempat pertama? Untuk alasan yang sama bahwa C # memiliki
public
,internal
(juga "tidak ada"),protected
,protected internal
, danprivate
, tetapi Jawa hanya memilikipublic
,protected
, apa-apa, danprivate
. Ini memberikan kontrol butir yang lebih baik atas perilaku apa yang Anda koding, dengan mengorbankan lebih banyak istilah dan kata kunci untuk dilacak.Dalam kasus
new
vsvirtual+override
, itu berlangsung seperti ini:abstract
, danoverride
dalam subclass.virtual
, danoverride
dalam subclass.new
dalam subclass.sealed
di kelas dasar.Sebagai contoh dunia nyata: Sebuah proyek yang saya kerjakan pada pesanan e-niaga olahan dari berbagai sumber. Ada basis
OrderProcessor
yang memiliki sebagian besar logika, dengan metodeabstract
/ tertentuvirtual
untuk menimpa kelas anak setiap sumber. Ini bekerja dengan baik, sampai kami mendapatkan sumber baru yang memiliki cara pemrosesan pesanan yang sama sekali berbeda, sehingga kami harus mengganti fungsi inti. Kami memiliki dua pilihan pada saat ini: 1) Tambahkanvirtual
ke metode dasar, danoverride
pada anak; atau 2) Tambahkannew
ke anak.Sementara salah satu dari mereka bisa bekerja, yang pertama akan membuatnya sangat mudah untuk menimpa metode tertentu itu lagi di masa depan. Itu akan muncul di lengkapi-otomatis, misalnya. Namun, ini adalah kasus yang luar biasa, jadi kami memilih untuk menggunakannya
new
. Itu mempertahankan standar "metode ini tidak perlu ditimpa", sementara memungkinkan untuk kasus khusus di mana ia melakukannya. Ini adalah perbedaan semantik yang membuat hidup lebih mudah.Apakah dicatat, bagaimanapun, bahwa ada adalah perbedaan perilaku yang terkait dengan ini, bukan hanya perbedaan semantik. Lihat artikel ini untuk detailnya. Namun, saya tidak pernah mengalami situasi di mana saya perlu memanfaatkan perilaku ini.
sumber
new
cara ini adalah bug yang menunggu untuk terjadi. Jika saya memanggil metode pada beberapa instance, saya berharap untuk selalu melakukan hal yang sama, bahkan jika saya melemparkan instance ke kelas dasarnya. Tapi itu bukan bagaimananew
metode ed berperilaku.new
adalah di WebViewPage <TModel> dalam kerangka kerja MVC. Tapi saya juga dilempar oleh bug yang melibatkannew
berjam-jam, jadi saya tidak berpikir itu adalah pertanyaan yang tidak masuk akal.Desain Java sedemikian rupa sehingga memberikan referensi ke suatu objek, panggilan ke nama metode tertentu dengan tipe parameter tertentu, jika diizinkan sama sekali, akan selalu memanggil metode yang sama. Mungkin saja konversi tipe parameter implisit dapat dipengaruhi oleh tipe referensi, tetapi begitu semua konversi tersebut telah diselesaikan, tipe referensi tersebut tidak relevan.
Ini menyederhanakan runtime, tetapi dapat menyebabkan beberapa masalah yang tidak menguntungkan. Misalkan
GrafBase
tidak mengimplementasikanvoid DrawParallelogram(int x1,int y1, int x2,int y2, int x3,int y3)
, tetapiGrafDerived
mengimplementasikannya sebagai metode publik yang menggambar jajaran genjang yang titik keempatnya dihitung berlawanan dengan yang pertama. Anggap lebih jauh bahwa versi yang lebih baruGrafBase
mengimplementasikan metode publik dengan tanda tangan yang sama, tetapi titik keempatnya dihitung berlawanan dengan yang kedua. Klien yang menerima mengharapkan aGrafBase
tetapi menerima referensi keGrafDerived
akan berharapDrawParallelogram
untuk menghitung titik keempat dengan caraGrafBase
metode baru , tetapi klien yang telah menggunakanGrafDerived.DrawParallelogram
sebelum metode dasar diubah akan mengharapkan perilaku yangGrafDerived
awalnya diterapkan.Di Jawa, tidak akan ada cara bagi penulis
GrafDerived
untuk membuat kelas itu hidup berdampingan dengan klien yang menggunakanGrafBase.DrawParallelogram
metode baru (dan mungkin tidak menyadari bahwaGrafDerived
ada) tanpa memutus kompatibilitas dengan kode klien yang ada yang digunakanGrafDerived.DrawParallelogram
sebelumGrafBase
mendefinisikannya. KarenaDrawParallelogram
tidak dapat mengetahui jenis klien yang memintanya, itu harus berperilaku identik ketika dipanggil oleh jenis kode klien. Karena kedua jenis kode klien memiliki ekspektasi yang berbeda mengenai bagaimana seharusnya berperilaku, tidak ada caraGrafDerived
untuk menghindari melanggar ekspektasi yang sah pada salah satu dari mereka (yaitu melanggar kode klien yang sah).Dalam C #, jika
GrafDerived
tidak dikompilasi ulang, runtime akan menganggap bahwa kode yang memanggilDrawParallelogram
metode berdasarkan referensi tipeGrafDerived
akan mengharapkan perilakuGrafDerived.DrawParallelogram()
telah ketika dikompilasi terakhir, tetapi kode yang memanggil metode berdasarkan referensi tipeGrafBase
akan diharapkanGrafBase.DrawParallelogram
(perilaku yang telah ditambahkan). JikaGrafDerived
nanti dikompilasi di hadapan yang disempurnakanGrafBase
, kompiler akan mengomel sampai programmer menentukan apakah metodenya dimaksudkan sebagai pengganti yang valid untuk anggota yang diwarisiGrafBase
, atau apakah perilakunya perlu dikaitkan dengan referensi tipeGrafDerived
, tetapi tidak boleh mengganti perilaku referensi tipeGrafBase
.Orang mungkin beralasan bahwa memiliki metode
GrafDerived
melakukan sesuatu yang berbeda dari anggotaGrafBase
yang memiliki tanda tangan yang sama akan menunjukkan desain yang buruk, dan karena itu tidak boleh didukung. Sayangnya, karena penulis tipe dasar tidak memiliki cara untuk mengetahui metode apa yang dapat ditambahkan ke tipe turunan, atau sebaliknya, situasi di mana klien kelas dasar dan kelas turunan memiliki harapan yang berbeda untuk metode yang dinamai seperti pada dasarnya tidak dapat dihindari kecuali tidak ada yang diizinkan untuk menambahkan nama yang mungkin juga ditambahkan oleh orang lain. Pertanyaannya bukan apakah duplikasi nama seperti itu harus terjadi, melainkan bagaimana meminimalkan kerugian ketika itu terjadi.sumber
DerivedGraphics? A class using
GrafDerived`?GrafDerived
. Saya mulai menggunakanDerivedGraphics
, tetapi merasa itu agak panjang. MeskipunGrafDerived
masih agak panjang, tetapi tidak tahu cara terbaik untuk memberi nama jenis grafik-renderer yang harus memiliki basis / hubungan turunan yang jelas.Situasi standar:
Anda adalah pemilik kelas dasar yang digunakan oleh banyak proyek. Anda ingin membuat perubahan pada kelas dasar yang akan memecah antara 1 dan kelas turunan yang tak terhitung jumlahnya, yang berada dalam proyek-proyek yang memberikan nilai dunia nyata (Kerangka kerja memberikan nilai terbaik sekaligus, tidak ada Manusia Nyata yang menginginkan Kerangka, mereka ingin hal berjalan di Framework). Semoga sukses dengan menyatakan kepada pemilik kelas yang sibuk yang sibuk; "Yah, kamu harus berubah, kamu seharusnya tidak mengganti metode itu" tanpa mendapatkan perwakilan sebagai "Kerangka Kerja: Penundaan Proyek dan Penyebab Bug" kepada orang-orang yang harus menyetujui keputusan.
Terutama karena, dengan tidak menyatakannya tidak dapat ditimpa, Anda secara implisit telah menyatakan bahwa mereka boleh melakukan hal yang sekarang mencegah perubahan Anda.
Dan jika Anda tidak memiliki sejumlah besar kelas turunan yang memberikan nilai dunia nyata dengan mengesampingkan kelas dasar Anda, mengapa itu adalah kelas dasar? Harapan adalah motivator yang kuat, tetapi juga cara yang sangat baik untuk berakhir dengan kode yang tidak direferensikan.
Hasil akhir: Kode kelas dasar kerangka kerja Anda menjadi sangat rapuh dan statis, dan Anda tidak dapat benar-benar melakukan perubahan yang diperlukan untuk tetap mutakhir / efisien. Atau, kerangka kerja Anda mendapat rep untuk ketidakstabilan (kelas turunan terus memecah) dan orang tidak akan menggunakannya sama sekali, karena alasan utama untuk menggunakan kerangka kerja adalah untuk membuat pengkodean lebih cepat dan lebih dapat diandalkan.
Sederhananya, Anda tidak dapat meminta pemilik proyek yang sibuk untuk menunda proyek mereka untuk memperbaiki bug yang Anda perkenalkan, dan mengharapkan sesuatu yang lebih baik daripada "pergi" kecuali Anda memberikan manfaat yang signifikan bagi mereka, bahkan jika "kesalahan" aslinya adalah milik mereka, yang bisa diperdebatkan.
Lebih baik untuk tidak membiarkan mereka melakukan hal yang salah di tempat pertama, di situlah "non-virtual secara default" masuk. Dan ketika seseorang mendatangi Anda dengan alasan yang sangat jelas mengapa mereka membutuhkan metode khusus ini untuk ditimpa, dan mengapa itu harus aman, Anda dapat "membuka" itu tanpa risiko melanggar kode orang lain.
sumber
Default ke non-virtual mengasumsikan bahwa pengembang kelas dasar sempurna. Dalam pengalaman saya, pengembang tidak sempurna. Jika pengembang kelas dasar tidak dapat membayangkan kasus penggunaan di mana metode dapat ditimpa atau lupa untuk menambahkan virtual maka saya tidak dapat mengambil keuntungan dari polimorfisme ketika memperluas kelas dasar tanpa memodifikasi kelas dasar. Dalam dunia nyata memodifikasi kelas dasar seringkali bukan pilihan.
Dalam C # pengembang kelas dasar tidak mempercayai pengembang subkelas. Di Jawa pengembang subkelas tidak mempercayai pengembang kelas dasar. Pengembang subclass bertanggung jawab atas subclass dan harus (imho) diberi kekuatan untuk memperluas kelas basis sesuai keinginan mereka (kecuali penolakan eksplisit, dan di java mereka bahkan dapat melakukan kesalahan ini).
Ini adalah properti mendasar dari definisi bahasa. Itu tidak benar atau salah, itu adalah apa itu dan tidak dapat berubah.
sumber