Mengapa C # dibuat dengan kata kunci "baru" dan "virtual + override" tidak seperti Java?

61

Di Jawa tidak ada virtual, new, overridekata 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 newatau virtual+deriveatau override virtual + baru bekerja.

Saya tidak dapat memahami mengapa di dunia ini saya akan menambahkan metode pada saya DerivedClassdengan nama dan tanda tangan yang sama dengan BaseClassdan mendefinisikan perilaku baru tetapi pada polimorfisme run-time, BaseClassmetode ini akan dipanggil! (yang tidak mengesampingkan tetapi secara logis seharusnya).

Jika virtual + overrideimplementasi 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 + overridealih-alih newdan juga penggunaan newalih-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 SpaceShipberasal dari Vehicle. Lalu saya ingin mengubah semua carmenjadi SpaceShipobjek 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 SpaceShiptidak menimpa Vehiclekelas ' acceleratedan gunakan newmaka logika kode saya akan rusak. Bukankah itu merugikan?

Anirban Nag 'tintinmj'
sumber
77
Anda hanya terbiasa dengan cara Jawa melakukannya, dan tidak meluangkan waktu untuk memahami kata kunci C # dengan istilah mereka sendiri. Saya bekerja dengan C #, segera memahami istilah-istilahnya, dan menemukan cara Java melakukannya dengan aneh.
Robert Harvey
12
IIRC, C # melakukan ini untuk "mendukung kejelasan." Jika Anda harus secara eksplisit mengatakan "baru" atau "menimpa," itu membuatnya jelas dan langsung jelas apa yang terjadi, daripada Anda harus mencari-cari di sekitar mencoba mencari tahu apakah metode ini menimpa beberapa perilaku di kelas dasar atau tidak. Saya juga merasa sangat berguna untuk dapat menentukan metode mana yang ingin saya tentukan sebagai virtual, dan mana yang tidak. (Java melakukan ini dengan final; itu justru sebaliknya).
Robert Harvey
15
“Di Jawa tidak ada kata kunci virtual, baru, yang menggantikan definisi metode.” Kecuali ada @Override.
svick
3
@svick anotasi dan kata kunci bukanlah hal yang sama :)
Anirban Nag 'tintinmj'

Jawaban:

86

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.

Ada beberapa alasan. Salah satunya adalah kinerja . Kita dapat mengamati bahwa ketika orang menulis kode di Jawa, mereka lupa menandai metode mereka final. Oleh karena itu, metode tersebut adalah virtual. Karena mereka virtual, mereka tidak berkinerja baik. Hanya ada overhead kinerja yang terkait dengan menjadi metode virtual. Itu satu masalah.

Masalah yang lebih penting adalah versi . Ada dua aliran pemikiran tentang metode virtual. Sekolah pemikiran akademis mengatakan, "Semuanya harus virtual, karena saya mungkin ingin menimpanya suatu hari nanti." Sekolah pemikiran pragmatis, yang berasal dari membangun aplikasi nyata yang berjalan di dunia nyata, mengatakan, "Kita harus benar-benar berhati-hati tentang apa yang kita buat virtual."

Ketika kita membuat sesuatu yang virtual dalam sebuah platform, kita membuat banyak sekali janji tentang bagaimana itu berkembang di masa depan. Untuk metode non-virtual, kami berjanji bahwa ketika Anda memanggil metode ini, x dan y akan terjadi. Ketika kami mempublikasikan metode virtual dalam API, kami tidak hanya berjanji bahwa ketika Anda memanggil metode ini, x dan y akan terjadi. Kami juga berjanji bahwa ketika Anda mengganti metode ini, kami akan menyebutnya dalam urutan khusus ini sehubungan dengan yang lain ini dan negara akan berada di ini dan itu invarian.

Setiap kali Anda mengatakan virtual dalam API, Anda membuat panggilan balik. Sebagai perancang kerangka kerja OS atau API, Anda harus benar-benar berhati-hati tentang hal itu. Anda tidak ingin pengguna mengesampingkan dan mengait pada titik sembarang dalam API, karena Anda tidak dapat selalu membuat janji-janji itu. Dan orang-orang mungkin tidak sepenuhnya memahami janji-janji yang mereka buat ketika mereka membuat sesuatu yang virtual.

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:

Saya tidak dapat memahami mengapa di dunia ini saya akan menambahkan metode dalam DerivedClass saya dengan nama dan tanda tangan yang sama dengan BaseClass dan mendefinisikan perilaku baru tetapi pada polimorfisme run-time, metode BaseClass akan dipanggil! (yang tidak mengesampingkan tetapi secara logis seharusnya).

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 newdan overridedi C #, lihat halaman MSDN ini ).

Skenario yang sangat praktis adalah ini:

  • Anda membuat API, yang memiliki kelas yang disebut Vehicle.
  • Saya mulai menggunakan API Anda dan berasal Vehicle.
  • VehicleKelas Anda tidak memiliki metode apa pun PerformEngineCheck().
  • Di Carkelas saya , saya menambahkan metode PerformEngineCheck().
  • Anda merilis versi baru API dan menambahkan PerformEngineCheck().
  • Saya tidak dapat mengganti nama metode saya karena klien saya bergantung pada API saya, dan itu akan menghancurkan mereka.
  • Jadi ketika saya mengkompilasi ulang terhadap API baru Anda, C # memperingatkan saya tentang masalah ini, misalnya

    Jika basis PerformEngineCheck()tidak virtual:

    app2.cs(15,17): warning CS0108: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    Use the new keyword if hiding was intended.

    Dan jika basisnya PerformEngineCheck()adalah virtual:

    app2.cs(15,17): warning CS0114: 'Car.PerformEngineCheck()' hides inherited member 'Vehicle.PerformEngineCheck()'.
    To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.
  • 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.

  • Dengan membuatnya new, saya tidak merusak klien saya jika fungsionalitas metode dasar berbeda dari metode turunan. Kode apa pun yang dirujuk Vehicletidak akan Car.PerformEngineCheck()dipanggil, tetapi kode yang memiliki referensi Carakan terus melihat fungsi yang sama dengan yang saya tawarkan PerformEngineCheck().

Contoh serupa adalah ketika metode lain di kelas dasar mungkin memanggil PerformEngineCheck()(terutama dalam versi yang lebih baru), bagaimana seseorang mencegahnya memanggil PerformEngineCheck()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 (melalui virtualkata kunci), dan pada kelas turunan (melalui newdan overridekata 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 newharus 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

Tetapi dalam kasus C # jika SpaceShip tidak menimpa kelas Kendaraan 'mempercepat dan menggunakan baru maka logika kode saya akan rusak. Bukankah itu merugikan?

Siapa yang memutuskan apakah SpaceShipbenar-benar mengesampingkan Vehicle.accelerate()atau jika itu berbeda? Itu harus menjadi SpaceShippengembang. Jadi jika SpaceShippengembang memutuskan bahwa mereka tidak menjaga kontrak kelas dasar, maka panggilan Anda untuk Vehicle.accelerate()tidak boleh pergi ke SpaceShip.accelerate(), atau haruskah itu? Saat itulah mereka akan menandainya sebagai new. Namun, jika mereka memutuskan bahwa itu memang mempertahankan kontrak, maka mereka sebenarnya akan menandainya override. Dalam kedua kasus, kode Anda akan berperilaku benar dengan memanggil metode yang benar berdasarkan kontrak . Bagaimana kode Anda memutuskan apakah SpaceShip.accelerate()benar-benar mengesampingkan Vehicle.accelerate()atau jika itu adalah tabrakan nama? (Lihat contoh saya di atas).

Namun, dalam kasus warisan implisit, bahkan jika SpaceShip.accelerate()tidak menjaga kontrak dari Vehicle.accelerate(), metode panggilan akan tetap pergi ke SpaceShip.accelerate().

Omer Iqbal
sumber
12
Titik kinerja benar-benar usang sekarang. Untuk bukti, lihat tolok ukur saya yang menunjukkan bahwa mengakses bidang melalui metode non-final tetapi tidak pernah kelebihan waktu membutuhkan satu siklus.
maaartinus
7
Tentu, mungkin itu masalahnya. Pertanyaannya adalah ketika C # memutuskan untuk melakukannya, mengapa melakukannya pada waktu ITU, dan karenanya jawaban ini valid. Jika pertanyaannya adalah apakah masih masuk akal, itu diskusi yang berbeda, IMHO.
Omer Iqbal
1
Saya sangat setuju dengan Anda.
maaartinus
2
IMHO, sementara pasti ada kegunaan untuk memiliki fungsi non-virtual, risiko jebakan yang tak terduga terjadi ketika sesuatu yang tidak diharapkan untuk menggantikan metode kelas dasar atau mengimplementasikan antarmuka, melakukannya.
supercat
3
@Nilzor: Oleh karena itu argumen mengenai akademik vs pragmatis. Secara pragmatis, tanggung jawab untuk memecahkan sesuatu terletak pada pembuat perubahan terakhir. Jika Anda mengubah kelas dasar Anda, dan kelas turunan yang ada tergantung padanya tidak berubah, itu bukan masalah mereka ("mereka" mungkin tidak ada lagi). Jadi kelas dasar menjadi terkunci dalam perilaku kelas turunannya, bahkan jika kelas itu seharusnya tidak virtual . Dan seperti yang Anda katakan, RhinoMock ada. Ya, jika Anda menyelesaikan semua metode dengan benar, semuanya setara. Di sini kami menunjuk pada setiap sistem Java yang pernah dibangun.
deworde
94

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.

Doval
sumber
7
Mengizinkan metode sewenang-wenang apa pun untuk ditimpa adalah salah. Anda hanya dapat mengganti metode-metode yang telah ditentukan oleh desainer superclass sebagai aman untuk diganti.
Doval
21
Eric Lippert membahas hal ini secara rinci dalam posnya, Metode Virtual dan Kelas Dasar yang Rapuh .
Brian
12
Java memiliki finalmodifier, jadi apa masalahnya?
Sarge Borsch
13
@corsiKa "objek harus terbuka untuk ekstensi" - mengapa? Jika pengembang kelas dasar tidak pernah berpikir tentang warisan, hampir dijamin bahwa ada dependensi dan asumsi halus dalam kode yang akan mengarah ke bug (ingat HashMap?). Entah desain untuk warisan dan membuat kontrak jelas atau tidak dan memastikan tidak ada yang bisa mewarisi kelas. @Overrideditambahkan 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.
Voo
9
@jwenting "Opini" adalah kata yang lucu; orang suka melampirkannya pada fakta sehingga mereka bisa mengabaikannya. Tetapi untuk apa pun nilainya, itu juga "pendapat" Joshua Bloch (lihat: Java Efektif , Butir 17 ), "pendapat" James Gosling (lihat wawancara ini ), dan sebagaimana ditunjukkan dalam jawaban Omer Iqbal , itu juga "pendapat" Anders Hejlsberg. (Dan meskipun dia tidak pernah berbicara tentang memilih masuk dan memilih keluar, Eric Lippert jelas setuju bahwa warisan juga berbahaya.) Jadi, siapa yang dimaksud?
Doval
33

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, dan private, tetapi Jawa hanya memiliki public, protected, apa-apa, dan private. 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 newvs virtual+override, itu berlangsung seperti ini:

  • Jika Anda ingin memaksa subclass untuk mengimplementasikan metode, gunakan abstract, dan overridedalam subclass.
  • Jika Anda ingin memberikan fungsionalitas tetapi mengizinkan subclass untuk menggantinya, gunakan virtual, dan overridedalam subclass.
  • Jika Anda ingin memberikan fungsionalitas yang tidak perlu ditimpa subclass, jangan gunakan apa pun.
    • Jika Anda kemudian memiliki subclass kasus khusus yang tidak perlu berperilaku berbeda, gunakan newdalam subclass.
    • Jika Anda ingin memastikan bahwa tidak ada subclass dapat pernah menimpa perilaku, menggunakan sealeddi kelas dasar.

Sebagai contoh dunia nyata: Sebuah proyek yang saya kerjakan pada pesanan e-niaga olahan dari berbagai sumber. Ada basis OrderProcessoryang memiliki sebagian besar logika, dengan metode abstract/ tertentu virtualuntuk 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) Tambahkan virtualke metode dasar, dan overridepada anak; atau 2) Tambahkan newke 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.

Bobson
sumber
7
Saya pikir sengaja menggunakan newcara 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 bagaimana newmetode ed berperilaku.
svick
@svick - Berpotensi, ya. Dalam skenario khusus kami, itu tidak akan pernah terjadi, tapi itu sebabnya saya menambahkan peringatan.
Bobson
Salah satu penggunaan yang sangat baik newadalah di WebViewPage <TModel> dalam kerangka kerja MVC. Tapi saya juga dilempar oleh bug yang melibatkan newberjam-jam, jadi saya tidak berpikir itu adalah pertanyaan yang tidak masuk akal.
pdr
1
@ C.Champagne: Alat apa pun dapat digunakan dengan buruk dan yang satu ini adalah alat yang sangat tajam - Anda dapat memotong sendiri dengan mudah. Tapi itu bukan alasan untuk menghapus alat dari kotak alat dan menghapus opsi dari perancang API yang lebih berbakat.
pdr
1
@vick Benar, itulah sebabnya biasanya peringatan kompiler. Tetapi memiliki kemampuan untuk "baru" mencakup kondisi tepi (seperti yang diberikan), dan bahkan lebih baik, membuatnya sangat jelas bahwa Anda melakukan sesuatu yang aneh ketika Anda datang untuk mendiagnosis bug yang tak terelakkan. "Kenapa kelas ini dan hanya kereta kelas ini ... ah-hah," baru ", ayo uji di mana SuperClass digunakan".
deworde
7

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 GrafBasetidak mengimplementasikan void DrawParallelogram(int x1,int y1, int x2,int y2, int x3,int y3), tetapi GrafDerivedmengimplementasikannya sebagai metode publik yang menggambar jajaran genjang yang titik keempatnya dihitung berlawanan dengan yang pertama. Anggap lebih jauh bahwa versi yang lebih baru GrafBasemengimplementasikan metode publik dengan tanda tangan yang sama, tetapi titik keempatnya dihitung berlawanan dengan yang kedua. Klien yang menerima mengharapkan a GrafBasetetapi menerima referensi ke GrafDerivedakan berharap DrawParallelogramuntuk menghitung titik keempat dengan cara GrafBasemetode baru , tetapi klien yang telah menggunakan GrafDerived.DrawParallelogramsebelum metode dasar diubah akan mengharapkan perilaku yang GrafDerivedawalnya diterapkan.

Di Jawa, tidak akan ada cara bagi penulis GrafDeriveduntuk membuat kelas itu hidup berdampingan dengan klien yang menggunakan GrafBase.DrawParallelogrammetode baru (dan mungkin tidak menyadari bahwa GrafDerivedada) tanpa memutus kompatibilitas dengan kode klien yang ada yang digunakan GrafDerived.DrawParallelogramsebelum GrafBasemendefinisikannya. Karena DrawParallelogramtidak 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 cara GrafDeriveduntuk menghindari melanggar ekspektasi yang sah pada salah satu dari mereka (yaitu melanggar kode klien yang sah).

Dalam C #, jika GrafDerivedtidak dikompilasi ulang, runtime akan menganggap bahwa kode yang memanggil DrawParallelogrammetode berdasarkan referensi tipe GrafDerivedakan mengharapkan perilaku GrafDerived.DrawParallelogram()telah ketika dikompilasi terakhir, tetapi kode yang memanggil metode berdasarkan referensi tipe GrafBaseakan diharapkan GrafBase.DrawParallelogram(perilaku yang telah ditambahkan). Jika GrafDerivednanti dikompilasi di hadapan yang disempurnakan GrafBase, kompiler akan mengomel sampai programmer menentukan apakah metodenya dimaksudkan sebagai pengganti yang valid untuk anggota yang diwarisi GrafBase, atau apakah perilakunya perlu dikaitkan dengan referensi tipe GrafDerived, tetapi tidak boleh mengganti perilaku referensi tipe GrafBase.

Orang mungkin beralasan bahwa memiliki metode GrafDerivedmelakukan sesuatu yang berbeda dari anggota GrafBaseyang 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.

supercat
sumber
2
Saya pikir ada jawaban yang bagus di sini, tetapi hilang di dinding teks. Silakan pisahkan ini menjadi beberapa paragraf lagi.
Bobson
Apa itu DerivedGraphics? A class using GrafDerived`?
C.Champagne
@ C.Champagne: Maksud saya GrafDerived. Saya mulai menggunakan DerivedGraphics, tetapi merasa itu agak panjang. Meskipun GrafDerivedmasih agak panjang, tetapi tidak tahu cara terbaik untuk memberi nama jenis grafik-renderer yang harus memiliki basis / hubungan turunan yang jelas.
supercat
1
@ Bobson: Lebih baik?
supercat
6

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.

deworde
sumber
0

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.

clish
sumber
2
ini sepertinya tidak menawarkan sesuatu yang substansial atas poin yang dibuat dan dijelaskan dalam 5 jawaban sebelumnya
agas