2011-04-28 8 views
23

Próbuję mieć abstrakcyjną klasę podstawową dla niektórych klas budowniczych, dzięki czemu mogę z łatwością ponownie użyć kodu między implementacjami Buildera. Chcę, aby moi konstruktorzy obsługiwali metodę łańcuchową, dlatego metoda musi zwracać "tę" instancję najbardziej konkretnego typu. Pomyślałem, że prawdopodobnie mógłbym to zrobić z lekami generycznymi. Niestety nie udało mi się tego zrobić bez użycia niebezpiecznych operacji. Czy to możliwe?Czy mogę mieć abstrakcyjną klasę konstruktora w języku Java z łańcuchem metod bez wykonywania niebezpiecznych operacji?

Przykładowy kod tego, jak próbuję go (i jak działa) poniżej. Chciałbym uniknąć rzutowania na T w "foo()" (co powoduje niezaznaczone ostrzeżenie), czy można to zrobić?

public class Builders 
{ 
    public static void main(final String[] args) 
    { 
     new TheBuilder().foo().bar().build(); 
    } 
} 


abstract class AbstractBuilder<T extends AbstractBuilder<?>> 
{ 
    public T foo() 
    { 
     // set some property 
     return (T) this; 
    } 
} 


class TheBuilder extends AbstractBuilder<TheBuilder> 
{ 
    public TheBuilder bar() 
    { 
     // set some other property 
     return this; 
    } 

    public Object build() 
    { 
     return new Object(); 
    } 
} 
+0

Jedną z cech płynnego interfejsu jest to, że istnieje domniemane założenie, że wszystkie metody zwracają "to", gdy prawnie nie muszą - mogą zwrócić zupełnie inny obiekt, o ile ma taki sam zwrot rodzaj. –

+0

Musisz tylko użyć javadoc. Albo w interfejsie, albo w klasie abstrakcyjnej. Użytkownicy klas muszą postępować zgodnie z umową zawartą przez javadoc. Nie ma nic więcej, co możesz zrobić jako projektant aplikacji (więcej, mniej). – mike

Odpowiedz

48

Chcesz zadeklarować T jak extends AbstractBuilder<T> w AbstractBuilder.

Użyj metody abstract protected, aby uzyskać this typu T.

abstract class AbstractBuilder<T extends AbstractBuilder<T>> { 
    protected abstract T getThis(); 

    public T foo() { 
     // set some property 
     return getThis(); 
    } 
} 


class TheBuilder extends AbstractBuilder<TheBuilder> { 
    @Override protected TheBuilder getThis() { 
     return this; 
    } 
    ... 
} 

Alternatywnie, spadek parametr typu rodzajowego, polegać na kowariantna rodzajów powrotów i dokonać czyszczenia kodu dla klientów (chociaż zwykle oni używać TheBuilder zamiast dużej mierze szczegóły implementacji klasy bazowej), jeżeli czyniąc wdrożenie bardziej szczegółowe.

+3

+1. Zrobiłem to wcześniej. Zwykle nazywam to 'me()' zamiast 'getThis()', więc koniec funkcji czyta 'return me();' –

+1

Działa to świetnie! Wielkie dzięki! –

+3

[FAQ Angeliki Langer: Jaka jest sztuczka "getThis"?] (Http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html # FAQ206) –

7

Jedną z alternatyw jest, aby nie używać rodzajowych, ale użyć nadpisania:

abstract class AbstractBuilder 
{ 
    public AbstractBuilder foo() 
    { 
     // set some property 
     return this; 
    } 
} 

class TheBuilder extends AbstractBuilder 
{ 
    @Override public TheBuilder foo() 
    { 
     super.foo(); return this; 
    } 
    public TheBuilder bar() 
    { 
     // set some other property 
     return this; 
    } 

    public Object build() 
    { 
     return new Object(); 
    } 
} 
+1

To jest właściwa droga do tego. Ta sytuacja jest idealnym przypadkiem dla dziedziczenia i polimorfizmu. Martwisz się o konstruktora, a konkretna instancja dba o szczegóły. Zrobiłbym 1 zmianę: czy program AbstractBuilder zaimplementuje interfejs taki jak Builder, który definiuje metody ... w ten sposób nie musisz ujawniać domyślnej i abstrakcyjnej funkcjonalności w ramach swojego API ... przepraszam, ogromny komentarz! –

+2

Dzięki, to działa! Niestety, moje metody w tych klasach Buildera są raczej niewielkie (zazwyczaj tak naprawdę ustawiają tylko pole, metoda build() robi magię), co sprawia, że ​​nadrzędne rozwiązanie wydaje się zbyt szczegółowe i utrudnia implementację nowych konkretnych Builderów (zawsze muszą zastąpić WSZYSTKIE metody klasy bazowej). –

+1

Wycofanie tego podejścia jest każda metoda łańcucha w AbstractBuilder musi być Zastąpione w podklasie, aby zwrócić podklasę. Cały punkt sztuczki getThis() polega na uniknięciu tego wymogu. – CandiedOrange

0

To powinno pomóc:

abstract class AbstractBuilder<T extends AbstractBuilder<?>> 
{ 
    public AbstractBuilder<T> foo() 
    { 
     // set some property 
     return (AbstractBuilder<T>) this; 
    } 

    abstract AbstractBuilder<T> bar(); 
    abstract Object build(); 
} 
+0

Problem, który próbuję rozwiązać, polega na tym, że różne implementacje budowania generują różne podklasy danego interfejsu, tj. Wszystkie konstrukcje abstrakcyjne generują obiekty implementujące IFoo, TheBuilder produkuje IFooBar, a TheOtherBuilder produkuje IBarFoo. Mają inny zestaw właściwości, które mogą być wypełniane przez konkretnych budowniczych. Przepraszamy za nie wyjaśnienie tego w moim pierwotnym pytaniu. Masz oczywiście rację, że build() powinien być metodą na AbstractBuilderze nie na TheBuilderze. –

Powiązane problemy