C # neden Java'dan farklı olarak "yeni" ve "sanal + geçersiz kıl" anahtar kelimelerle yapıldı?


61

Java'da hiç orada virtual, new, overrideyöntem tanımı için anahtar kelimeler. Dolayısıyla bir yöntemin çalışmasının anlaşılması kolaydır. Eğer DerivedClass , BaseClass'ı uzatır ve aynı isimde ve BaseClass'ın aynı imzasına sahip bir metoda sahipse, geçersiz kılma, çalışma zamanı polimorfizminde (yöntemin olmaması şartıyla static) gerçekleşir.

BaseClass bcdc = new DerivedClass(); 
bcdc.doSomething() // will invoke DerivedClass's doSomething method.

Şimdi C # 'a gelin newya virtual+deriveda yeni + sanal geçersiz kılmanın nasıl çalıştığını anlamak çok karışık ve zor olabilir .

Neden dünyada benim adıma DerivedClassaynı adı ve imzasını taşıyan bir yöntem ekleyeceğimi BaseClassve yeni bir davranış tanımlayacağımı anlayamıyorum ama çalışma zamanı polimorfizminde bu BaseClassyöntem çağrılacak! (geçersiz kılmıyor ama mantıklı olmalı).

Durumunda virtual + overridemantıksal uygulama doğru fakat programcı o kodlama sırasında geçersiz kılmak için kullanıcıya izin vermelidir hangi yöntemi düşünmek zorundadır ama. Bazı yanlısı yanlısı vardır (şimdi oraya gidelim).

Peki neden C # için çok alan vardır un -Mantıksal muhakeme ve karışıklık. Ben de kullanım düşünmelidir ki gerçek dünya bağlamında olduğu gibi sorumu reframe olabilir virtual + overrideyerine newve ayrıca kullanımı newyerine virtual + override?


Özellikle Omar'dan gelen çok güzel cevapların ardından , C # tasarımcılarının, programcıların bir yöntem yapmadan önce düşünmeleri gereken, Java'nın bazı çaylak hatalarını iyi yapan ve iyi olan bir yöntem yapmadan önce daha fazla stres verdiklerini anlıyorum.

Şimdi aklımda bir sorum var. Java gibi bir kodum olsaydı

Vehicle vehicle = new Car();
vehicle.accelerate();

ve daha sonra SpaceShiptüretilmiş yeni bir sınıf yapıyorum Vehicle. O zaman hepsini carbir SpaceShipnesneye çevirmek istiyorum Sadece kod satırını değiştirmem gerekiyor

Vehicle vehicle = new SpaceShip();
vehicle.accelerate();

Bu, herhangi bir kod noktasındaki mantığımın hiçbirini bozmaz.

Fakat C # durumunda SpaceShip, Vehiclesınıfı geçersiz kılmazsa accelerateve kullanırsanız new, kodumun mantığı bozulur. Bu bir dezavantaj değil mi?


77
Java'nın yaptığı gibi alışkınsınız ve C # anahtar kelimelerini kendi terimleriyle anlamak için zaman ayırmadınız. C # ile çalışıyorum, şartları hemen anladım ve Java'nın garip davranma şeklini buldum.
Robert Harvey,

12
IIRC, C # bunu "netliği artırmak" için yaptı. Açıkça “yeni” veya “geçersiz kıl” demek zorundaysanız, yöntemin bir temel sınıftaki bazı davranışları geçersiz kılıp kılmayacağını anlamaya çalışmaktan vazgeçmek yerine, ne olduğunu açıkça ve anında ortaya çıkarır. Ayrıca hangi yöntemleri sanal olarak belirtmek istediğimi, hangilerini istemediğimi belirtebilmeyi çok yararlı buluyorum. (Java bunu yapar final; tam tersi bir yoldur).
Robert Harvey,

15
“Java'da, yöntem tanımı için sanal, yeni, geçersiz kılan anahtar kelimeler yok.” Ancak var @Override.
svick


3
@svick ek açıklama ve anahtar kelimeler aynı şey değil :)
Anirban Nag 'tintinmj'

Yanıtlar:


86

C # neden böyle yaptığını sorduğundan beri, C # yaratıcılarına sorman en iyisi. Onlar bir in (Java gibi) varsayılan olarak sanal ile gitmemesi için seçtim Anders Hejlsberg, C # için kurşun mimar, cevap mülakat , ilgili parçacıkları aşağıda belirtilmiştir.

Bir yöntemi sanal olmayan olarak işaretlemek için Java'nın varsayılan olarak son anahtar kelimeyle sanal olduğunu unutmayın. Hala öğrenmesi gereken iki kavram var, ancak çoğu kişi son anahtar kelimeyi bilmiyor veya proaktif olarak kullanmıyor. C #, bilinçli olarak bu kararları vermek için sanal ve yeni / geçersiz kılmayı kullanmaya zorlar.

Birkaç sebep var. Biri performans . İnsanlar Java’da kod yazarken, yöntemlerini kesin olarak işaretlemeyi unuttuklarını görebiliyoruz. Bu nedenle, bu yöntemler sanaldır. Sanal oldukları için de performans göstermiyorlar. Sanal bir yöntem olmakla ilgili sadece performans ek yükü var. Bu bir sorun.

Daha önemli bir konu versiyonlama . Sanal yöntemler hakkında iki düşünce okulu vardır. Düşüncenin akademik okulu, "Her şey sanal olmalı, çünkü bir gün geçersiz kılmak isteyebilirim" diyor. Gerçek dünyada çalışan gerçek uygulamalar oluşturmaktan gelen pragmatik düşünce okulu, "Sanallaştırdıklarımıza gerçekten çok dikkat etmeliyiz" diyor.

Bir platformda sanal bir şey yaptığımızda, gelecekte nasıl geliştiği konusunda çok fazla söz veriyoruz. Sanal olmayan bir yöntem için, bu yöntemi çağırdığınızda, x ve y'nin olacağına söz veriyoruz. Bir API'de sanal bir yöntem yayınladığımızda, yalnızca bu yöntemi çağırdığınızda x ve y'nin olacağına söz vermeyiz. Ayrıca, bu yöntemi geçersiz kıldığınızda, diğerlerine nazaran bu belirli sıraya göre arayacağımıza ve devletin bu durumda ve o değişmez olacağına söz veriyoruz.

Bir API'de sanal olarak her söylediğinizde, geri arama kancası yaratıyorsunuz. Bir işletim sistemi veya API çerçeve tasarımcısı olarak, bu konuda gerçekten dikkatli olmalısınız. Kullanıcıların bir API'deki herhangi bir noktada geçersiz kılmalarını ve takmalarını istemezsiniz, çünkü bu sözleri vermeniz gerekmez. Ve insanlar sanal bir şey yaptıklarında verdikleri sözleri tam olarak anlayamayabilirler.

Mülakat, geliştiricilerin sınıf mirası tasarımı hakkında nasıl düşündüğü ve bunun kararlarına nasıl yol açtığı hakkında daha fazla tartışmaya sahiptir.

Şimdi şu soruya:

Neden dünyada DerivedClass'ımda BaseClass ile aynı isimde ve aynı imzayla bir yöntem ekleyeceğimi ve yeni bir davranış tanımlayacağımı anlayamıyorum ama çalışma zamanı polimorfizminde BaseClass yöntemi çağrılacak! (geçersiz kılmıyor ama mantıklı olmalı).

Bu, türetilmiş bir sınıfın, temel sınıfın sözleşmesine uymadığını, aynı adı taşıyan bir yöntemi olduğunu beyan etmek istediğinde olur. ( C # newve overridearasındaki farkı bilmeyenler için bu MSDN sayfasına bakın ).

Çok pratik bir senaryo şudur:

  • Sınıfı olan bir API oluşturdunuz Vehicle.
  • API'nizi kullanmaya başladım ve türetildim Vehicle.
  • Sizin Vehiclesınıf herhangi bir yöntemi yoktu PerformEngineCheck().
  • Benim içinde Carsınıfa, bir yöntemi ekleyin PerformEngineCheck().
  • API'nizin yeni bir versiyonunu yayınladınız ve a eklediniz PerformEngineCheck().
  • İstemcilerim API'ma bağlı olduğundan ve yöntemimi değiştireceği için yöntemimi yeniden adlandıramıyorum.
  • Yeni API’nize karşı yeniden derlediğimde, C # bu konuda beni uyarıyor;

    Baz PerformEngineCheck()değilse virtual:

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

    Ve baz eğer PerformEngineCheck()idi 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.
    
  • Şimdi, sınıfımın gerçekten temel sınıfın sözleşmesini uzatıp uzatmayacağı ya da farklı bir sözleşme olup olmadığı ancak aynı isimde olacağı konusunda açıkça bir karar vermeliyim.

  • Bunu yaparak new, temel yöntemin işlevselliği türetilmiş yöntemden farklıysa, müşterilerimi bozmam. Herhangi başvurulan kod Vehiclegörmeyeceklerinden Car.PerformEngineCheck()bir başvuru vardı aradım ama kod için Carben de teklif ettiğini aynı işlevselliği görmeye devam edecektir PerformEngineCheck().

Benzer bir örnek, temel sınıftaki başka bir yöntemin PerformEngineCheck()(özellikle de yeni sürümde) çağrılabileceği durumlarda PerformEngineCheck(), türetilmiş sınıfın aranmasını nasıl önler ? Java'da bu karar temel sınıfa aittir, ancak türetilen sınıf hakkında hiçbir şey bilmiyor. C # 'da, bu karar hem temel sınıfa ( virtualanahtar kelime aracılığıyla ) hem de türetilmiş sınıfa ( newve overrideanahtar kelimeler aracılığıyla ) dayanır .

Elbette, derleyicinin attığı hatalar, programcıların beklenmedik bir şekilde hata yapmamaları için de yararlı bir araç sağlar (yani, bunu gerçekleştirmeden geçersiz kılar veya yeni işlevler sağlar).

Anders'in dediği gibi, gerçek dünya bizi sıfırdan başlasak asla girmek istemediğimiz sorunlara zorlar.

EDIT: newArayüz uyumluluğunu sağlamak için nerede kullanılması gerektiğine dair bir örnek eklendi .

EDIT: Yorumlardan geçerken, diğer örnek senaryolarda (Brian'ın bahsettiği) Eric Lippert'in (sonra C # tasarım komitesinin üyelerinden biri) yazdığı bir yazıyla da karşılaştım.


BÖLÜM 2: Güncellenmiş soruya göre

Fakat eğer C # durumunda eğer SpaceShip Araç sınıfını geçersiz kılmazsa 'hızlanır ve yeni kullanırsanız, kodumun mantığı bozulur. Bu bir dezavantaj değil mi?

Kimin SpaceShipgerçekten geçersiz kılmaya Vehicle.accelerate()ya da farklı olup olmadığına kim karar veriyor ? SpaceShipGeliştirici olmalı . Eğer SpaceShipgeliştirici, temel sınıfın sözleşmesini yerine getirmediğine karar verirse, o zaman aramanız Vehicle.accelerate()gitmeli SpaceShip.accelerate()mi, yoksa gitmeli mi? O zaman bunu işaretleyecekler new. Bununla birlikte, sözleşmeyi gerçekten tutturmaya karar verirse, o zaman aslında onu işaretleyecektir override. Her iki durumda da, sözleşmenize bağlı olarak doğru yöntemi arayarak kodunuz doğru şekilde davranır . Kodunuz SpaceShip.accelerate()gerçekten geçersiz kılmaya Vehicle.accelerate()ya da bir isim çarpışması olup olmadığına nasıl karar verebilir ? (Yukarıdaki örneğime bakın).

Ancak, örtülü miras durumunda, bile SpaceShip.accelerate()tutmadı sözleşme ait Vehicle.accelerate(), yöntem çağrısı hala gider SpaceShip.accelerate().


12
Performans noktası şimdiye kadar tamamen eskidir. Bir Kanıt olarak benim bkz kriter bir yoluyla bir alana erişim olduğunu gösteren sivil finali aşırı yöntem ama asla tek bir döngü sürer.
maaartinus

7
Elbette, durum bu olabilir. Asıl soru, C # yapmaya karar verdiğinde, neden bu zamanda bunu yaptı ve bu nedenle bu cevap geçerliydi. Eğer soru hala mantıklı mı yoksa, bu farklı bir tartışma, IMHO.
Ömer İkbal

1
Sana tamamen katılıyorum.
maaartinus

2
IMHO, kesinlikle sanal olmayan fonksiyonlara sahip olmak için kullanılsa da, beklenmedik tuzaklar riski, bir temel sınıf yöntemini geçersiz kılması beklenmeyen veya böyle bir arayüzü uygulayan bir şey olduğunda ortaya çıkar.
supercat

3
@Nilzor: Bu nedenle akademik vs. pragmatik hakkındaki tartışma. Pragmatik olarak, bir şeyleri kırma sorumluluğu son değiştiriciye aittir. Temel sınıfınızı değiştirirseniz ve mevcut türetilmiş bir sınıf değişmemesine bağlıdır, bu onların sorunu değil ("onlar" artık mevcut olmayabilir). Böylece temel sınıflar türetilmiş sınıfların davranışlarına kilitlenir, o sınıf asla sanal olmamalıydı . Ve dediğiniz gibi, RhinoMock var. Evet, tüm yöntemleri doğru uygularsanız, her şey eşdeğerdir. Burada şimdiye kadar oluşturulmuş her Java sistemini işaret ediyoruz.
deworde

94

Yapıldı çünkü yapılacak doğru şey bu. Gerçek şu ki, tüm yöntemlerin geçersiz kılınmasına izin verilmesi yanlış; kırılgan temel sınıf sorununa yol açar, burada temel sınıftaki bir değişikliğin alt sınıfları kırabildiğini söyleme imkanınız yoktur. Bu nedenle, geçersiz kılınmaması gereken yöntemleri kara listeye almalı veya geçersiz kılınmasına izin verilenleri beyaz listeye almalısınız. İkisinden beyaz liste yalnızca daha güvenli değil ( yanlışlıkla kırılgan bir temel sınıf oluşturamadığınız için ), kompozisyon lehine kalıtımdan kaçınmanız gerektiğinden, daha az iş gerektiriyor.


7
Herhangi bir rastgele yöntemin geçersiz kılınmasına izin vermek yanlıştır. Yalnızca üst sınıf tasarımcının geçersiz kılmak için güvenli olarak tasarladığı yöntemleri geçersiz kılabilirsiniz.
Doval

21
Eric Lippert, görevindeki Sanal Yöntemler ve Kırılgan Temel Sınıflarında bu konuyu ayrıntılı olarak tartışıyor .
Brian

12
Java'nın finaldeğiştiricisi var, peki sorun ne?
Sarge Borsch

13
@ corsiKa "nesneler uzantıya açık olmalı" - neden? Temel sınıfın geliştiricisi miras hakkında hiç düşünmediyse, kodda hatalara yol açacak ince bağımlılıklar ve varsayımlar olduğu neredeyse garanti edilir (hatırladın HashMapmı?). Miras için tasarım yapın ya da sözleşmeyi netleştirin ya da yapmayın ve kimsenin sınıfa miras alamayacağından emin olun. @Overridebir bandaid olarak eklendi, çünkü davranışları o zamana kadar değiştirmek için çok geçti, fakat orijinal Java devleri (özellikle Josh Bloch) bile bunun kötü bir karar olduğu konusunda hemfikir.
Voo

9
@jwenting "Görüş" komik bir kelimedir; İnsanlar bunu gerçeklere eklemeyi sever, böylece onları göz ardı edebilirler. Ancak buna değer ne olursa olsun, aynı zamanda Joshua Bloch'un "görüşü" (bakınız: Etkili Java , Madde 17 ), James Gosling'in "görüşü" ( bu röportajı görün ) ve Ömer İkbal'in cevabında da belirtildiği gibi, Anders Hejlsberg'in "görüşü" de. (Ve dışarı atmayı seçmemeyi hiç ele almamış olmasına rağmen, Eric Lippert açıkça mirasın da tehlikeli olduğunu kabul eder.) Peki kime atıfta bulunuyor?
Doval

33

Robert Harvey'in dediği gibi, hepsi senin alıştığın şeyde. Java'nın bu esneklik eksikliğini garip buluyorum .

Dedi, neden bu ilk etapta var? C # sahip olduğu Aynı nedenle public, internal(ayrıca "hiçbir şey") protected, protected internalve privatefakat Java sadece vardır public, protectedhiçbir şey ve private. Kodladığınız şeyin davranışı üzerinde daha iyi tahıl kontrolü sağlar, takip edilmesi gereken daha fazla terim ve anahtar kelime olması pahasına.

Durumunda newvs virtual+override, bu böyle şeyler:

  • Yöntemi uygulamak için alt sınıfları zorlamak istiyorsanız, kullanın abstractve overridealt sınıfta.
  • Eğer işlevsellik sağlamak ancak alt sınıf gelmesini sağlamasını istiyorsanız, kullanmak virtualve overridealt sınıfta.
  • Alt sınıfların hiçbir zaman geçersiz kılmaması gereken işlevsellik sağlamak istiyorsanız, hiçbir şey kullanmayın.
    • Sonra özel bir durum alt sınıfı varsa yok farklı davranır gerekir kullanmak newalt sınıfta.
    • Eğer hiçbir alt sınıf sağlamak istiyorsanız hiç davranışı geçersiz kılmak, kullanmak sealedtaban sınıfta.

Gerçek dünyadan bir örnek için: Birçok farklı kaynaktan işlenmiş e-ticaret siparişleri üzerinde çalıştığım bir proje. Her kaynağın alt sınıfının geçersiz kılması için OrderProcessorbelirli abstract/ virtualyöntemlerle mantığın çoğuna sahip bir temel vardı . Tamamen farklı bir işleme emri olan yeni bir kaynağımız olana kadar , çekirdek işlevini değiştirmek zorunda kaldığımız sürece, bu iyi sonuç verdi. Bu noktada iki seçeneğimiz vardı: 1) virtualTemel yönteme ekle ve overrideçocukta; veya 2) newÇocuğa ekleyin .

Her ikisi de çalışabilirken, birincisi, gelecekte bu belirli yöntemi geçersiz kılmayı çok kolaylaştıracaktır. Örneğin, otomatik tamamlamada görünecekti. Ancak bu istisnai bir durumdu, bu yüzden kullanmayı tercih ettik new. "Bu yöntemin geçersiz kılınmasına gerek yok" standardını koruyan, yaptığı özel durum için izin verirken. Hayatı kolaylaştıran anlamsal bir fark.

Oradaki Ancak dikkat musunuz olan bu ile ilişkili bir davranış farkı, sadece bir semantik farkı. Ayrıntılar için bu makaleye bakın. Ancak, asla bu davranıştan yararlanmam gereken bir durumla karşılaşmadım.


7
Kasten newbu şekilde kullanmanın gerçekleşmeyi bekleyen bir böcek olduğunu düşünüyorum. Bazı örneklerde bir yöntem çağırırsam, örneği temel sınıfına alsam bile, her zaman aynı şeyi yapmasını bekliyorum. Ama newed yöntemlerinin davranışı böyle değil .
svick

@svick - Potansiyel olarak, evet. Özel senaryomuzda, bu asla gerçekleşmeyecek, ancak bu yüzden uyarıyı ekledim.
Bobson

Mükemmel bir kullanımı newMVC çerçevesinde WebViewPage <TModel> içindedir. Ama aynı zamanda newsaatlerce süren bir böcek tarafından da atıldım , bu yüzden bunun mantıksız bir soru olduğunu sanmıyorum.
pdr

1
@ C.Champagne: Herhangi bir alet kötü bir şekilde kullanılabilir ve bu özellikle keskin bir alettir - kendinizi kolayca kesebilirsiniz. Ancak bu, aracı araç kutusundan kaldırmak ve daha yetenekli bir API tasarımcısından bir seçeneği kaldırmak için bir neden değildir.
pdr

1
@svick Correct, bu yüzden normalde bir derleyici uyarısı. Ancak "yeni" olma yeteneğine sahip olmak uç koşulları (verilen gibi) kapsar ve hatta daha da iyisi, kaçınılmaz olan hatayı teşhis etmeye başladığınızda garip bir şey yaptığınızı gerçekten açıkça gösterir. "Neden bu sınıf ve sadece bu sınıf buggy ... ah-hah," yeni ", hadi SuperClass'ın kullanıldığı yerde test edelim".
deworde

7

Java'nın tasarımı, bir nesneye herhangi bir referans verildiğinde, belirli parametre türleriyle belirli bir yöntem adına yapılan bir çağrıya, izin veriliyorsa, her zaman aynı yöntemi çağırır. Örtük parametre tipi dönüşümlerin bir referansın türünden etkilenmesi mümkündür, ancak bu tür tüm dönüşümler çözüldükten sonra referansın türü ilgisizdir.

Bu, çalışma zamanını basitleştirir, ancak bazı talihsiz sorunlara neden olabilir. Diyelim ki GrafBaseuygulamıyor void DrawParallelogram(int x1,int y1, int x2,int y2, int x3,int y3), ama GrafDerivedhesaplanan dördüncü noktası birincinin karşısında olan bir paralelkenar çizen bir kamu yöntemi olarak uyguluyor. Daha sonraki bir versiyonun GrafBaseaynı imzayla bir genel yöntem, ancak ikincisinin karşısındaki hesaplanan dördüncü noktası olduğunu varsayalım . Yeni bir yöntemin modasında öne çıkan noktanın hesaplanmasını beklerken GrafBaseancak referans almayı bekleyen müşteriler, ancak temel yöntem değiştirilmeden önce kullanan müşteriler başlangıçta uygulanan davranışı bekleyeceklerdir .GrafDerivedDrawParallelogramGrafBaseGrafDerived.DrawParallelogramGrafDerived

Java'da, yazarın, GrafDerivedbu sınıfı GrafBase.DrawParallelogram, GrafDeriveddaha GrafDerived.DrawParallelogramönce GrafBasetanımlanmış olan mevcut müşteri koduyla uyumluluğu bozmadan , yeni yöntemi kullanan (ve hatta var olan habersiz olabilir) müşterileriyle bir arada yaşatması mümkün olmazdı . Yana DrawParallelogrammüşterinin cins bir çağırma ne söyleyemem istemci kodunun türüne göre çağrıldığında, bu aynı şekilde davranması gerekir. İki tür müşteri kodunun nasıl davranması gerektiğine ilişkin farklı beklentileri olduğundan GrafDerived, bunlardan birinin meşru beklentilerini ihlal etmekten kaçınmanın bir yolu yoktur (yani meşru müşteri kodunu çiğnemek).

C # 'da, GrafDerivedyeniden derlenmemişse, çalışma zamanı DrawParallelogram, tür referansları üzerine yöntemi çağıran kodun , en son derlendiğinde GrafDeriveddavranışın beklediğini GrafDerived.DrawParallelogram(), ancak tür başvurusu üzerine yöntemi çağıran kodun GrafBaseolacağını bekler GrafBase.DrawParallelogram(davranış türü eklendi). Eğer GrafDerivedsonradan gelişmiş varlığında derlenmiş çekirdekler GrafBase, derleyici programcı kadar ötmeyeceksin ya onun yöntemi miras üye için geçerli bir yedek olarak düşünülmüştü belirtir edecek GrafBase, ya da davranış tipi referansları bağlı gerekip gerekmediğini GrafDerived, ancak olmamalı tür referanslarının davranışını değiştirir GrafBase.

Bir kimse, aynı imzaya sahip GrafDerivedbir üyeden farklı bir şey yapma yönteminin GrafBasekötü bir tasarıma işaret edeceğini ve bunun desteklenmemesi gerektiğini makul bir şekilde savunabilir . Maalesef, bir baz türünün yazarı, türetilmiş türlere hangi yöntemlerin eklenebileceğini bilmenin hiçbir yolu olmadığından, veya bunun tersi olduğunda, temel sınıf ve türetilmiş sınıf istemcilerinin benzer adlandırılmış yöntemler için farklı beklentileri olduğu durumlar esasen kaçınılmazdır. kimsenin başkasının ekleyebileceği herhangi bir isim de eklenemez. Asıl soru, böyle bir isim çoğaltmanın gerçekleşip gerçekleşmemesi gerektiği değil, zedelendiğinde zararın nasıl en aza indirileceğidir.


2
Bence burada iyi bir cevap var ama metin duvarında kayboluyor. Lütfen bunu birkaç paragrafa ayırın.
Bobson,

Nedir DerivedGraphics? A class using GrafDerived`?
C.Champagne

@ C.Champagne: Demek istedim GrafDerived. Kullanmaya başladım DerivedGraphicsama biraz uzun olduğunu hissettim. Hatta GrafDerivedhala biraz uzun, ama net bir temel / türetilmiş bir ilişki olması gereken grafik oluşturucu türlerini nasıl adlandıracağımızı bilmiyordum.
supercat

1
@ Bobson: Daha iyi?
supercat

6

Standart durum:

Birden fazla proje tarafından kullanılan bir temel sınıfın sahibisiniz. Gerçek dünyaya değer sağlayan projelerde yer alan 1 ile sayısız türetilmiş sınıf arasında kırılacak olan söz konusu temel sınıfta bir değişiklik yapmak istiyorsunuz (Bir çerçeve en iyi ihtimalle bir çıkarmadan değer sağlar, hiçbir Gerçek İnsan Varlığı bir Çerçeve istemez, isterler) Çerçeve üzerinde çalışan şey). Türetilmiş sınıfların meşgul sahiplerine iyi şanslar; "Eh, değiştirmek zorundasınız, kararları onaylamak zorunda olan kişilere" Çerçeve: Projelerin Gecikmesi ve Hataların Etkisi "olarak bir temsilci kazanmadan bu yöntemi geçersiz kılmamalısınız.

Özellikle, geçersiz kılmayacağını ilan ederek, şu anda değişikliğinizi önleyen bir şeyi yapmanın uygun olduğunu açık bir şekilde beyan ettiniz.

Üstelik, temel sınıfınızı geçersiz kılarak gerçek dünya değeri sağlayan önemli sayıda türetilmiş sınıfa sahip değilseniz, neden ilk etapta bir temel sınıf? Umut, güçlü bir motive edicidir, fakat aynı zamanda, referanssız bir kodla baş etmek için de çok iyi bir yoldur.

Sonuç: Çerçeve temel sınıf kodunuz inanılmaz derecede kırılgan ve statik hale gelir ve mevcut / verimli kalmak için gerçekten gerekli değişiklikleri yapamazsınız. Alternatif olarak, çerçeveniz kararsızlık için bir itibar kazanıyor (türetilmiş sınıflar bozulmaya devam ediyor) ve insanlar hiç kullanmayacak, çünkü bir çerçeveyi kullanmanın temel nedeni kodlamayı daha hızlı ve daha güvenilir hale getirmektir.

Basitçe söylemek gerekirse, meşgul proje sahiplerinden tanıttığınız hataları düzeltmek için projelerini ertelemelerini talep edemez ve orijinal "hata" bile olsa, onlara önemli faydalar sağlamadığınız sürece "uzaklaşmaktan" daha iyi bir şey bekleyemezsiniz. En iyisi tartışmalı olan onlarındı.

Yanlış olanı yapmalarına izin vermemek daha iyidir, buradaki "varsayılan olarak sanal olmayan" ifadesinin girdiği yer. Ve birileri size çok açık bir nedenle geldiğinde neden bu özel yöntemin geçersiz kılınması gerektiğini ve neden güvenli olmalıdır, başkasının kodunu kırma riski olmadan "kilidini açabilirsiniz" .


0

Sanal olmayanı varsayılan yapmak, temel sınıf geliştiricinin mükemmel olduğunu varsayar. Tecrübelerime göre geliştiriciler mükemmel değildir. Bir temel sınıfın geliştiricisi, bir yöntemin geçersiz kılınabileceği veya sanal eklemeyi unuttuğu bir kullanım durumu hayal edemezse, temel sınıfı değiştirmeden temel sınıfı genişletirken polimorfizmden yararlanamıyorum. Gerçek dünyada, temel sınıfı değiştirmek genellikle bir seçenek değildir.

C # 'da temel sınıf geliştirici alt sınıf geliştiriciye güvenmez. Java'da alt sınıf geliştirici, temel sınıf geliştiriciye güvenmez. Alt sınıf geliştirici, alt sınıftan sorumludur ve (imho) temel sınıfı uygun gördükleri şekilde genişletme gücü verilmelidir (açık reddetmeyi yasaklar ve java'da bunu yanlış anlayabilirler).

Dil tanımının temel bir özelliğidir. Doğru ya da yanlış değil, olduğu şeydir ve değişemez.


2
Bu, önceki 5
cevapta
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.