2016-07-24 42 views
7

Załóżmy, że mamy interfejs nadrzędny z funkcją compare().Java Polimorfizm: Jak mogę uniknąć wprowadzania parametrów wejściowych?

public interface Parent { 
    public int compare(Parent otherParent); 
} 

Załóżmy, że dzieci child1, Child2, Child3 wdrożenia tego interfejsu Parent

public class Child1 implements Parent { 
    @Override 
    public int compare(Parent other) { 
     Child1 otherChild = (Child1)other; 
    } 
} 

Również używam rodzajowych <T extends Parent> wszędzie indziej w kodzie. Muszę więc porównać dwa obiekty typu T z innych części kodu.

Rozumiem, że jest to zły projekt, ponieważ próbuję typować obiekt nadrzędny w funkcji compare() i nie wiem nawet, czy dane wejściowe są typu Child1.

Jak mogę uniknąć tego typu rzucania podczas używania generycznych?

Odpowiedz

2

Nie możesz. Interfejsy Java są dokładnie tym, na co wskazuje nazwa - interfejs dyktujący typy akceptowane; więc musi być dokładnie ten sam podpis metody w czasie kompilacji, jak zdefiniowano w interfejsie.

Tak, odesłanie go z powrotem do typu Dziecko wydaje się złym projektem - ale to naprawdę zły projekt, który Java narzuca, więc to nie twoja wina.

+0

Guess będę musiał użyć typu cast wtedy. Dzięki! –

3

Odpowiedź zależy od tego, jak traktujesz swoją hierarchię dziedziczenia:

  • Jeśli klasy pochodne muszą porównać do wystąpień tylko własnej klasy i mogą rzucać błąd podczas przedstawiane z obiektu innej klasy, używaj odlewania, ale chroń je sprawdzając, czy klasy pochodne muszą być porównywane z granicami typów, aby uniknąć rzutowania, wymagana jest bardziej złożona strategia: możesz użyć wzorca podobnego do gościa, aby zaimplementować podwójną wysyłkę wymaganą do wykonania porównania.

Oto próbka realizacja przy użyciu klasy abstrakcyjnej:

// Use this class as the base class of your Child classes 
class AbstractChild implements Parent { 
    @Override 
    public int compare(Parent otherObj) { 
     if (!()) { 
      throw new IllegalStateException("Unexpected implementation of Child"); 
     } 
     AbstractChild other = (AbstractChild)otherObj; 
     return doCompare(other); 
    } 
    protected abstract int doCompare(AbstractChild other); 
    protected abstract int accept(Child1 c1); 
    protected abstract int accept(Child2 c2); 
    protected abstract int accept(Child3 c3); 
} 
class Child1 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 
class Child2 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 
class Child3 extends AbstractChild { 
    @Override 
    protected int doCompare(AbstractChild other) { 
     return other.accept(this); 
    } 
    @Override 
    protected int accept(Child1 c1) { 
     // Compare c1 instance to this object 
    } 
    @Override 
    protected int accept(Child2 c2) { 
     // Compare c2 instance to this object 
    } 
    @Override 
    protected int accept(Child3 c3) { 
     // Compare c3 instance to this object 
    } 
} 

Jedyny odlew w tym podejściu jest na poziomie klasy abstrakcyjnej. Implementacje rzeczywistej logiki porównania są zawarte w metodach accept, gdzie dokonuje się porównania między dwoma obiektami znanego typu.

+0

Muszę przyznać, że jestem trochę przygnębiony, jeśli chodzi o zaletę posiadania tego kodu w porównaniu z metodą typu castin/checking typu in-method. W ostatnim dziesięcioleciu nie byłem zbytnim ekspertem od Javy (kiedy od tego czasu wyszła Java 1.4), więc: Czy pozwala JVM dokonywać porównań typów koniecznych do "delegowania" do właściwej metody, naprawdę o dowolnej wartości wydajności lub ma inne zalety (poza tym, że prawdopodobnie jest "właściwą rzeczą do zrobienia", pozwalając na silnie typowane ujednoznacznianie typu obsługi języka)? –

+0

@ MarcusMüller To jest odpowiedź na pytanie "jak uniknąć obsady", która zakłada, że ​​z jakiegoś powodu chcesz tego uniknąć. W przeszłości stosowałem podobne podejście w bardzo specyficznych sytuacjach, kiedy mogłem wykorzystać gościa do czegoś więcej niż porównania. Przejdę z Java do C# w czasie, kiedy wyszła Java-5, i użyłem innego mechanizmu językowego do implementacji mojej wielokrotnej wysyłki (mianowicie 'dynamic', który jest dostępny od C# 4). – dasblinkenlight

+0

Zgadzam się z tym, że mój komentarz jest nieco nietypowy :) Najwyraźniej wzorzec odwiedzającego jest * klasycznym podejściem do odzyskiwania informacji o typie utraconej przez przekazywanie rzeczy w języku polimorficznym, który jest ograniczony do czystego radzenia sobie z obiektami informacji o różnych typach (porównywanie Javy z C#, a nawet dynamicznie wpisywanych języków skryptowych, takich jak Python, gdzie mogłem rzeczywiście używać typów jako klucza do dyktafonu kalendarzy, jeśli chciałem). Moją nadzieją było po prostu to, że można było doświadczyć odrobiny doświadczenia na tym, co zrobiliście: dziękuję! –

2

Nie można tego uniknąć w ogólnym przypadku. Jednakże, jeżeli liczba klas potomnych zostanie rozwiązany, można zastosować coś zbliżonego do odwiedzający uniknąć odlewania kosztem pisać więcej kod:

interface Parent { 
    int compare(Parent p); 
    protected int compareWith(Child1 c); 
    protected int compareWith(Child2 c); 
    protected int compareWith(Child3 c); 
} 

class Child1 implements Parent { 
    @Override int compare(Parent p) { 
     return p.compareWith(this); 
    } 
    @Override int compareWith(Child1 c) { 
     //specific code to child1 
    } 
    @Override int compareWith(Child2 c) { 
     //specific code to child2 
    } 
    @Override int compareWith(Child3 c) { 
     //specific code to child3 
    } 
} 
// and so on for the other children 

ten unika casting, ale nie jestem pewien, że dodatkowy wysiłek jest wart tego w przypadku, który tu przedstawiłeś.

5

Dlaczego nie to?

interface Parent<T extends Parent<?>> { 
    int compare(T type); 
} 

class Child1 implements Parent<Child1>{ 

    @Override 
    public int compare(Child1 type) { 
     return 0; 
    } 
} 

Edit: Aby zapewnić prawidłowe użytkowanie można użyć

interface Parent<T extends Parent<T>>{ /* ... */ } //instead of wildcard 

Ale szczerze mówiąc, że „pętla” nie wygląda całkiem, a ponieważ rodzajowych Java nie działa przy starcie (more information), są one w istocie syntaktycznym cukrem dla tej samej obsady, którą nazwałeś "złym projektem", więc nie sądzę, żeby twoje obecne podejście było złe.

+1

Część "extends Parent " rzeczywiście nie wygląda ładnie. Ale w rzeczywistości * można * pominąć - zakładając, że implementor jest odpowiedzialny za użycie właściwego 'T'. To samo zrobiono w 'Porównywalnym'. – Marco13

2

Wzór, który tam opisałeś, to dokładnie wzór Comparable. W rzeczywistości należy rozważyć pominięcie interfejsu Parent i zastąpić go numerem Comparable.

Interfejs Comparable i jego zastosowań pokazują również, w jaki sposób ten może być rozwiązany: Typ może być podana jako parametr, aby upewnić się, że tylko typ dopasowania mogą być przekazywane do metody compare:

interface Parent<T> { 
    int compare(T that); 
} 

class Child1 implements Parent<Child1> { 
    @Override 
    public int compare(Child1 that) { 
     ... 
    } 
} 

We wszystkich innych przypadkach, to przynajmniej trzeba myśleć o tym, co powinno się zdarzyć, gdy różne Child wyraĹĽenie -lekcje porównywane są do siebie:

Child1 c1 = new Child1(); 
Child2 c2 = new Child2(); 

// Should this be possible? What should happen here? 
// How should different child classes be compared? 
c1.compare(c2);