2012-06-10 16 views
17

Pracuję nad problemem, w którym jest kilka implementacji Foo, wraz z kilkoma FooBuilder. Podczas gdy Foo ma kilka wspólnych zmiennych, które muszą być ustawione, mają również różne zmienne, które wymagają ich odpowiednich FooBuilder do realizacji niektórych konkretnych funkcji. Dla zwięzłości, chciałbym mieć FooBuilder „s ustawiające użyć metody łańcuchowym, jak:Java: powracająca podklasa w sygnaturze metody superklasy

public abstract class FooBuilder { 
    ... 

    public FooBuilder setA(int A) { 
    this.A = A; 
    return this; 
    } 

    ... 
} 

i

public class FooImplBuilder extends FooBuilder{ 
    ... 
    public FooImplBuilder setB(int B) { 
    this.B = B; 
    return this; 
    } 
    public FooImplBuilder setC(int C) { 
    this.C = C; 
    return this; 
    } 
    ... 
} 

i tak dalej, z kilku różnych FooBuilder wdrożeń. Technicznie robi to wszystko, co chcę, jednak to podejście jest wrażliwe na kolejność wywołań metod, gdy wykonywane jest łańcuchowanie metod. Poniżej podano metodę niezdefiniowanych błędów kompilacji:

someFoo.setA(a).setB(b)... 

Wymaganie, aby deweloper pomyślał o kolejności wywołań metod w łańcuchu. Aby tego uniknąć, chciałbym, aby ustawiający w FooBuilder w jakiś sposób zwrócili rzeczywistą podklasę wykonawczą. Jednak nie jestem pewien, jak to zrobić. Jakie jest najlepsze podejście?

+0

Wymusza również rzucanie we wszystkie strony lub uniemożliwia zmianę właściwości klas nadrzędnych, kiedy chcesz. –

Odpowiedz

13

To jest dobre pytanie i prawdziwy problem.

Najprostszym sposobem radzenia sobie z tym w Javie jest prawdopodobnie użycie generycznych, jak wspomniano w odpowiedzi Jochena.

Jest spora dyskusja o tym problemie i rozsądnym rozwiązaniem w tego wpisu na blogu na Using Inheritance with Fluent Interfaces, który łączy rodzajowych z definicji getThis() metody nadpisane w podklasy konstruktora do rozwiązania problemu zawsze powrocie budowniczym właściwej klasy.

+1

Podoba mi się to rozwiązanie. O wiele bardziej wyszukane niż to, co zaproponowałem, ale także bardziej elastyczne i ostatecznie eleganckie. – Jochen

+0

Po prostu podsumuj połączony blog i usuń oddzielne budownicze: 'public abstract class X > {public int a; public B setA (int foo) {this.a = foo; return getThis(); public abstract B getThis();} public class Y extends X {public int b; public Y setB (int bar) {this.b = bar; return getThis();} public Y getThis() {return this;}} ' –

3

Generics może być sposobem, aby przejść tutaj.

Jeśli zadeklarujesz Seta() coś jak to (pseudo-kod)

<T> T setA(int a) 

kompilator powinien być w stanie dowiedzieć się prawdziwe typy, a jeśli nie można dać wskazówki w kodzie jak

obj.<RealFoo>setA(42) 
+3

+1: Dobry opis tej taktyki można znaleźć na stronie http://egalluzzo.blogspot.com/2010/06/using-inheritance-with-fluent.html –

+0

Kompilator nie * potrafi * wykombinować typu w łańcuchu wywołanie metody i powtarzanie typu dla każdej metody wywołania nie jest opcją. – meriton

+0

@DonRoby: Dlaczego nie zamieścisz tego jako odpowiedzi? Zdarza się, że jest lepszym rozwiązaniem niż Jochen's i na pewno zasługuje na uptoote ;-) – meriton

1

Po znalezieniu this excellent answer udostępniam je teraz.

public class SuperClass<I extends SuperClass> 
{ 
    @SuppressWarnings("unchecked") // If you're annoyed by Lint. 
    public I doStuff(Object withThings) 
    { 
     // Do stuff with things. 
     return (I)this ; // Will always cast to the subclass. Causes the Lint warning. 
    } 
} 

public class ImplementationOne 
extends SuperClass<ImplementationOne> 
{} // doStuff() will return an instance of ImplementationOne 

public class ImplementationTwo 
extends SuperClass<ImplementationTwo> 
{} // doStuff() will return an instance of ImplementationTwo 
Powiązane problemy