Downcasting nasıl önlenir?


12

Sorum süper sınıf Animal'in özel bir vakası hakkında.

  1. Benim Animalcan moveForward()ve eat().
  2. Sealuzanır Animal.
  3. Doguzanır Animal.
  4. Ve Animaladı da verilen özel bir yaratık var Human.
  5. Humanayrıca bir yöntem speak()uygular (tarafından uygulanmadı Animal).

AnimalKabul eden soyut bir yöntemin uygulanmasında yöntemi kullanmak istiyorum speak(). Mahzun olmadan bu mümkün görünmüyor. Jeremy Miller makalesinde mahzun bir koku olduğunu yazdı .

Bu durumda küçümsemekten kaçınmak için bir çözüm ne olurdu?


6
Modelinizi düzeltin. Sınıf hiyerarşisini modellemek için mevcut taksonomik hiyerarşiyi kullanmak genellikle yanlış bir fikirdir. Soyutlamalarınızı mevcut koddan almalı, soyutlama oluşturmamalı ve sonra kodu ona uydurmaya çalışmalısınız.
Euphoric

2
Hayvanların konuşmasını istiyorsanız, o zaman konuşma yeteneği, bir şeyi hayvan yapan şeyin bir parçasıdır: artima.com/interfacedesign/PreferPoly.html
JeffO

8
moveForward? yengeçler ne olacak?
Fabio Marcolini

Etkili Java, BTW'de hangi öğe var?
goldilocks

1
Bazı insanlar konuşamaz, bu yüzden denerseniz bir istisna atılır.
Tulains Córdova

Yanıtlar:


12

Bir Humanşey yapmak için belirli sınıfın tür olup olmadığını bilmeniz gereken bir yönteminiz varsa , özellikle bazı SOLID ilkelerini ihlal ediyorsunuz :

  • Açık / kapalı prensibi - gelecekte, konuşabilen (örneğin bir papağan) yeni bir hayvan türü eklemeniz veya bu türe özgü bir şey yapmanız gerekiyorsa, mevcut kodunuzun değişmesi gerekecektir
  • Arayüz ayırma ilkesi - çok fazla genelleme yaptığınız anlaşılıyor. Bir hayvan çok çeşitli türleri kapsayabilir.

Kanımca, yönteminiz belirli bir sınıf türünü bekliyorsa, bu yöntemi belirli bir yöntem olarak adlandırmak için, o yöntemi yalnızca arabirimi değil, yalnızca o sınıfı kabul edecek şekilde değiştirin.

Bunun gibi bir şey:

public void MakeItSpeak( Human obj );

ve bunun gibi değil:

public void SpeakIfHuman( Animal obj );

Ayrıca Animalçağrılan üzerinde soyut bir yöntem yaratılabilir canSpeakve her somut uygulama "konuşup konuşamayacağını" tanımlamalıdır.
Brandon

Ama cevabınızı daha çok seviyorum. Çok karmaşıklık, bir şeyi olmayan bir şeye benzemeye çalışmaktan gelir.
Brandon

@ Sanırım, bu durumda, "konuşamazsa", bir istisna atar. Veya sadece hiçbir şey yapmayın.
BЈовић

Böylece aşırı public void makeAnimalDoDailyThing(Animal animal) {animal.moveForward(); animal.eat()}public void makeAnimalDoDailyThing(Human human) {human.moveForward(); human.eat(); human.speak();}
Bart Weber

1
Tüm yol boyunca gidin - makeItSpeak (ISpeakingAnimal animal) - daha sonra ISpeakingAnimal'i Animal uygulayabilirsiniz. Ayrıca makeItSpeak (Hayvan hayvanı) {ISpeakingAnimal örneği varsa konuşun}, ancak bunun hafif bir kokusu olabilir.
Haziran'da ptyx

6

Sorun sizin aşağı iniyor olmanız değil, aşağı iniyor olmanız Human. Bunun yerine bir arayüz oluşturun:

public interface CanSpeak{
    void speak();
}

public abstract class Animal{
    //....
}

public class Human extends Animal implements CanSpeak{
    public void speak(){
        //....
    }
}

public void mysteriousMethod(Animal animal){
    //....
    if(animal instanceof CanSpeak){
        ((CanSpeak)animal).speak();
    }else{
        //Throw exception or something
    }
    //....
}

Bu şekilde koşul, hayvanın olması değildir Human- koşul, konuşabilmesidir. Bu mysteriousMethod, Animaluyguladıkları sürece insan olmayan diğer alt sınıflarla çalışabileceği anlamına gelir CanSpeak.


bu durumda o argüman olarak yerine Hayvan CanSpeak bir örneğini almalı
Newtopian

1
@ Newtopian Bu yöntemin hiçbir şeye ihtiyaç duymadığını Animalve bu yöntemin tüm kullanıcılarının, kendisine bir CanSpeaktür başvurusu (hatta Humantür başvurusu) aracılığıyla göndermek istedikleri nesneyi tutacağını varsayarsak . Eğer durum buysa, bu yöntem Humanilk etapta kullanılabilirdi ve bunu tanıtmamız gerekmeyecekti CanSpeak.
Idan Arye

Arabirimden kurtulmak, yöntem imzasında spesifik olmakla bağlantılı değildir, ancak yalnızca İnsanlar varsa ve yalnızca "CanSpeak" olan insanlar olacaksa arabirimden kurtulabilirsiniz.
Newtopian

1
@Newtopian CanSpeakİlk etapta tanıtmamızın nedeni , onu uygulayan bir şeye sahip olmamamız ( Human) değil, onu kullanan bir şeye (metot) sahip olmamızdır. Noktası CanSpeakbeton sınıfından bu yöntemi ayrıştırmak için olan Human. Eğer CanSpeakfarklı şeyleri tedavi edecek yöntemlerimiz olmasaydı, bu şeyleri ayırt etmenin bir anlamı olmazdı CanSpeak. Sadece bir yöntemimiz olduğu için bir arayüz oluşturmuyoruz ...
Idan Arye

2

Communicate'a Animal'i ekleyebilirsiniz. Köpek havlıyor, İnsan konuşuyor, Mühür .. ahh .. Mührün ne yaptığını bilmiyorum.

Ancak, (Hayvan İnsandır) Speak ();

Sormak isteyebileceğiniz soru, alternatif nedir? Neyi başarmak istediğinizi tam olarak bilmediğim için bir öneri vermek zor. Downcasting / upcasting'in en iyi yaklaşım olduğu teorik durumlar vardır.


15
Mühür ow ow gider , ama tilki tanımsız.

2

Bu durumda, varsayılan uygulaması speak()içinde AbstractAnimalsınıfında olacaktır:

void speak() throws CantSpeakException {
  throw new CantSpeakException();
}

Bu noktada, Abstract sınıfında varsayılan bir uygulamanız vardır - ve doğru şekilde davranır.

try {
  thingy.speak();
} catch (CantSeakException e) {
  System.out.println("You can't talk to the " + thingy.name());
}

Evet, bu, her şeyi işlemek için kod aracılığıyla dağılmış deneme yakalamalarınız olduğu anlamına gelir speak, ancak bunun alternatifi if(thingy is Human)bunun yerine tüm konuşmaları sarmaktır.

İstisnanın avantajı, bir noktada (papağan) konuşan başka bir şey varsa, tüm testlerinizi yeniden uygulamanıza gerek kalmamasıdır.


1
(Python kodu olmadığı sürece) bu istisna kullanmak için iyi bir neden olduğunu sanmıyorum.
Bryan Chen

1
Oy kullanmam çünkü teknik olarak işe yarayacaktı, ama bu tür bir tasarımı gerçekten sevmiyorum. Neden ebeveyn bir ebeveynin düzeyinde bir yöntemi tanımlamak olamaz uygulamak? Nesnenin ne tür olduğunu bilmiyorsanız (ve bunu yaptıysanız - bu sorunu yaşamazsanız), tamamen önlenebilir bir istisna olup olmadığını kontrol etmek için her zaman bir try / catch kullanmanız gerekir. Bunu canSpeak()daha iyi ele almak için bir yöntem bile kullanabilirsiniz .
Brandon

2
Birisinin, eski bir uygulama kullandıkları için bir istisna atmak için bir dizi çalışma yöntemini yeniden yazma kararından kaynaklanan bir ton kusuru olan bir proje üzerinde çalıştım. Uygulamayı düzeltebilirdi, ancak bunun yerine her yere istisnalar atmayı seçti. Doğal olarak, QA'ya yüzlerce hata ile girdik. Bu yüzden çağrılmaması gereken yöntemlerde istisnalar atmaya karşı önyargılıyım. Eğer çağrılmaları gerekmiyorsa, silin .
Brandon

2
Bu durumda bir istisna atmaya alternatif hiçbir şey yapmıyor olabilir. Sonuçta, konuşamıyorlar :)
BЈовић

@Brandon, daha sonradan uyarlama ve istisna hariç tasarımın başlaması arasında bir fark var. Java8'de varsayılan bir yöntemle bir arayüze bakabilir veya bir istisna atmayabilir ve sadece sessiz olabilirsiniz. Önemli olan nokta, aşağı inmeye ihtiyaç duymamak için, işlevin geçirilen tipte tanımlanması gerektiğidir.

1

Downcasting bazen gerekli ve uygundur. Özellikle, bir yeteneğe sahip olabilen veya olmayabilen nesnelerin olduğu durumlarda ve bu yeteneğin bazı varsayılan biçimlerde bu yetenek olmadan nesneleri ele alırken kullanmak istediği durumlarda genellikle uygundur. Basit bir örnek olarak, a'nın Stringbaşka bir keyfi nesneye eşit olup olmadığını sorduğunu varsayalım . Birinin Stringdiğerine eşit Stringolması için, diğer dizenin uzunluk ve destek karakter dizisini incelemesi gerekir. Bir Eğer Stringbir eşittir olmadığı sorulur Dogancak bu uzunluğunu erişemez Dog, ama gerek olmamalıdır; bunun yerine, Stringa'nın kendisini karşılaştırması beklenen nesne birStringkarşılaştırmada varsayılan bir davranış kullanılmalıdır (diğer nesnenin eşit olmadığını bildirme).

Düşüşün en şüpheli olduğu zaman, dökülmekte olan nesnenin uygun tipte "bilindiği" zamandır. Bir nesne olduğu bilinmektedir, genel olarak, Catbir türde bir değişken kullanmalıdır Catyerine tipte bir değişken daha Animalbaşvurmak için. Ancak bunun her zaman işe yaramadığı zamanlar vardır. Örneğin, bir Zookoleksiyon, her çiftteki nesnelerin, diğer çiftlerdeki nesnelere etki edemeseler bile, birbirlerinin üzerinde hareket edebileceği beklentisiyle çift / tek dizi yuvalarındaki nesne çiftlerini tutabilir. Böyle bir durumda, her çiftteki nesnelerin, sözdizimsel olarak , herhangi başka bir çiftten nesneleri geçirebilecekleri şekilde, spesifik olmayan bir parametre tipini kabul etmeleri gerekecektir . Böylece bile Cat'ınplayWith(Animal other)yöntem, yalnızca zaman çalışacak otherbir oldu Cat, Zoobuna bir unsurunu geçmek mümkün olması gerekir Animal[]parametresinin türü olması gerekir, böylece Animalyerine Cat.

Küçültmenin meşru bir şekilde kaçınılmaz olduğu durumlarda, kişi bunu niteliksiz kullanmalıdır. Kilit soru, birinin ne zaman aşağı inmesini mantıklı bir şekilde önleyebileceğini belirlemek ve mantıklı bir şekilde mümkün olduğunda kaçınmaktır.


Dize durumunda, bir yönteme sahip olmalısınız Object.equalToString(String string). Sonra boolean String.equal(Object object) { return object.equalStoString(this); }, yokuş aşağı gerek yok: dinamik dağıtım kullanabilirsiniz.
Giorgio

@Giorgio: Dinamik dağıtımın kullanımları vardır, ancak genel olarak downcasting'ten daha da kötüdür.
supercat

dinamik dağıtım genellikle downcasting daha kötü? i rağmen başka bir yol
Bryan Chen

@BryanChen: Bu sizin terminolojinize bağlıdır. ObjectHerhangi bir equalStoStringsanal yöntem olduğunu düşünmüyorum ve alıntılanan örneğin Java'da nasıl çalışacağını bilmediğimi itiraf edeceğim, ancak C # 'da dinamik dağıtım (sanal dağıtımdan farklı olarak) derleyicinin aslında Sanal dağıtımdan farklı bir sınıfta bir yöntem ilk kez kullanıldığında Yansıma tabanlı ad araması yapmak (bu, yalnızca geçerli bir yöntem adresi içermesi gereken sanal yöntem tablosundaki bir yuva aracılığıyla çağrı yapar).
supercat

Modelleme açısından, genel olarak dinamik dağıtımı tercih ederim. Aynı zamanda, girdi parametrelerinin türüne göre bir prosedür seçmenin nesne yönelimli yoludur.
Giorgio

1

Hayvanları kabul eden soyut bir yöntemin uygulanmasında speak () yöntemini kullanmak istiyorum.

Birkaç seçeneğiniz var:

  • speakVarsa aramak için yansımayı kullanın . Avantaj: bağımlılık yok Human. Dezavantaj: Artık "speak" adına gizli bir bağımlılık var.

  • Yeni bir arayüz tanıtın Speakerve arayüze indirin. Bu, belirli bir beton tipine bağlı olarak daha esnektir. Bu değiştirmek zorunda dezavantajı vardır Humanuygulamak Speaker. Değiştiremezseniz bu çalışmazHuman

  • Aşağı doğru Human. Bu, başka bir alt sınıfın konuşmasını istediğinizde kodu değiştirmeniz gerekecek dezavantaja sahiptir. İdeal olarak, tekrar tekrar geri dönüp eski kodu değiştirmeden kod ekleyerek uygulamaları genişletmek istersiniz.

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.