2009-08-19 10 views
129

Mam rodzajowego interfejsuJak utworzyć klasę Java, która implementuje jeden interfejs z dwoma rodzajami generycznymi?

public interface Consumer<E> { 
    public void consume(E e); 
} 

Mam klasy, która pobiera dwa typy obiektów, więc chciałbym zrobić coś takiego:

public class TwoTypesConsumer implements Consumer<Tomato>, Consumer<Apple> 
{ 
    public void consume(Tomato t) { ..... } 
    public void consume(Apple a) { ...... } 
} 

Widocznie nie mogę tego zrobić.

Mogę oczywiście zrealizować wysyłkę samodzielnie, np.

public class TwoTypesConsumer implements Consumer<Object> { 
    public void consume(Object o) { 
     if (o instanceof Tomato) { ..... } 
     else if (o instanceof Apple) { ..... } 
     else { throw new IllegalArgumentException(...) } 
    } 
} 

Ale szukam w czasie kompilacji typu kontroli i wysyłki rozwiązanie, które zapewni rodzajowych.

Najlepszym rozwiązaniem, jakie mogę wymyślić, jest zdefiniowanie oddzielnych interfejsów, np.

public interface AppleConsumer { 
    public void consume(Apple a); 
} 

Funkcjonalnie to rozwiązanie jest OK, jak sądzę. To po prostu gadatliwe i brzydkie.

Wszelkie pomysły?

+0

Dlaczego potrzebujesz dwóch ogólnych interfejsów tego samego typu podstawowego? – akarnokd

+5

Z powodu usunięcia typu nie można tego zrobić. Zachowaj dwie różne klasy, które implementują konsumenta. Tworzy więcej małych klas, ale zachowuje ogólny kod (nie używaj zaakceptowanej odpowiedzi, łamie całą koncepcję ... nie możesz traktować TwoTypesConsumer jako konsumenta, który jest ZŁY). –

Odpowiedz

67

Rozważmy enkapsulacji:

public class TwoTypesConsumer { 
    private TomatoConsumer tomatoConsumer = new TomatoConsumer(); 
    private AppleConsumer appleConsumer = new AppleConsumer(); 

    public void consume(Tomato t) { 
     tomatoConsumer.consume(t); 
    } 

    public void consume(Apple a) { 
     appleConsumer.consume(a); 
    } 

    public static class TomatoConsumer implements Consumer<Tomato> { 
     public void consume(Tomato t) { ..... } 
    } 

    public static class AppleConsumer implements Consumer<Apple> { 
     public void consume(Apple a) { ..... } 
    } 
} 

Jeśli tworzenia tych statycznych klas wewnętrznych przeszkadza, można użyć anonimowego klasy:

public class TwoTypesConsumer { 
    private Consumer<Tomato> tomatoConsumer = new Consumer<Tomato>() { 
     public void consume(Tomato t) { 
     } 
    }; 

    private Consumer<Apple> appleConsumer = new Consumer<Apple>() { 
     public void consume(Apple a) { 
     } 
    }; 

    public void consume(Tomato t) { 
     tomatoConsumer.consume(t); 
    } 

    public void consume(Apple a) { 
     appleConsumer.consume(a); 
    } 
} 
+1

jakoś tak wygląda jak duplikacja kodu ... Napotkałem ten sam problem i nie znalazłem żadnego innego rozwiązania, które wygląda na czyste. –

+78

Ale 'TwoTypesConsumer' spełnia * nie * umowy, więc o co chodzi? Nie można go przekazać do metody, która wymaga albo typu "konsumenta". Cała idea konsumenta dwuskładnikowego polegałaby na tym, że można go podać metodą, która chce konsumenta pomidorów, a także metodą, która chce konsumenta jabłkowego. Tutaj nie mamy ani. –

+0

@JeffAxelrod Uczyniłbym klasy wewnętrzne niestatycznymi, aby w razie potrzeby mieli dostęp do załączającej instancji 'TwoTypesConsumer', a następnie można przekazać' twoTypesConsumer.getAppleConsumer() 'metodzie, która chce konsumenta Apple. Inną opcją byłoby dodanie metod podobnych do 'addConsumer (Producent producenta)' do TwoTypesConsumer. – herman

30

Z powodu usunięcia typu nie można dwukrotnie zaimplementować tego samego interfejsu (z innymi parametrami typu).

+5

Widzę, jak to jest problem ... pytanie brzmi: jaki jest najlepszy (najbardziej skuteczny, bezpieczny, elegancki) sposób na ominięcie tego problemu. – daphshez

+1

Bez wchodzenia w logikę biznesową, coś tutaj "pachnie" jak wzór Odwiedzającego. –

7

Przynajmniej można zrobić małą poprawę swojej realizacji wysyłki wykonując coś jak następuje:

public class TwoTypesConsumer implements Consumer<Fruit> { 

Fruit bycia przodkiem Pomidor i Apple.

+10

Dzięki, ale cokolwiek mówią profesjonaliści, nie uważam pomidora za owoc.Niestety nie ma wspólnej klasy bazowej innej niż Object. – daphshez

+1

Zawsze możesz utworzyć klasę podstawową o nazwie: AppleOrTomato;) –

+1

Lepiej dodaj owoc, który dzieli się albo z Apple, albo z pomidorem. –

10

Oto możliwe rozwiązanie oparte na Steve McLeod's one:

public class TwoTypesConsumer { 
    public void consumeTomato(Tomato t) {...} 
    public void consumeApple(Apple a) {...} 

    public Consumer<Tomato> getTomatoConsumer() { 
     return new Consumer<Tomato>() { 
      public void consume(Tomato t) { 
       consumeTomato(t); 
      } 
     } 
    } 

    public Consumer<Apple> getAppleConsumer() { 
     return new Consumer<Apple>() { 
      public void consume(Apple a) { 
       consumeApple(t); 
      } 
     } 
    } 
} 

Wymownym wymaganiem pytania było Consumer<Tomato> i Consumer<Apple> obiektów, które współdzielą stan. Potrzeba obiektów Consumer<Tomato>, Consumer<Apple> pochodzi z innych metod, które oczekują tych parametrów jako parametrów. Potrzebuję jednej klasy, aby zaimplementować je zarówno w celu współdzielenia państwa.

Założeniem Steve'a było użycie dwóch klas wewnętrznych, z których każda implementuje inny rodzajowy.

Ta wersja dodaje obiekty pobierające dla obiektów implementujących interfejs klienta, które można następnie przekazać innym metodom oczekującym na nie.

+1

Jeśli ktoś tego używa: warto przechowywać instancje 'Consumer <*>' w polach instancji, jeśli 'get * Consumer' jest często wywoływany. – TWiStErRob

3

po prostu natknąłem się na to. To po prostu się stało, że miałem ten sam problem, ale rozwiązać go w inny sposób: właśnie stworzył nowy interfejs podobny do tego

public interface TwoTypesConsumer<A,B> extends Consumer<A>{ 
    public void consume(B b); 
} 

Niestety, jest to uważane za Consumer<A> a nie jako Consumer<B> wbrew wszelkiej logice .Więc trzeba stworzyć mały zasilacz dla drugiego konsumenta jak to wewnątrz klasy

public class ConsumeHandler implements TwoTypeConsumer<A,B>{ 

    private final Consumer<B> consumerAdapter = new Consumer<B>(){ 
     public void consume(B b){ 
      ConsumeHandler.this.consume(B b); 
     } 
    }; 

    public void consume(A a){ //... 
    } 
    public void conusme(B b){ //... 
    } 
} 

jeśli Consumer<A> jest potrzebna, można po prostu przejść this, a jeśli Consumer<B> potrzebna jest po prostu przekazać consumerAdapter

+0

[Daphna] (http://stackoverflow.com/a/1298751/253468) Odpowiedź jest taka sama, ale czystsza i mniej skomplikowana. – TWiStErRob

1

Nie możesz wykonaj to bezpośrednio w jednej klasie, ponieważ definicja klasy poniżej nie może zostać skompilowana z powodu usunięcia typów ogólnych i zduplikowania deklaracji interfejsu.

class TwoTypesConsumer implements Consumer<Apple>, Consumer<Tomato> { 
// cannot compile 
... 
} 

Każde inne rozwiązanie do pakowania takie same zużywają operacje w jednej klasie wymaga, aby określić swoją klasę jako:

class TwoTypesConsumer { ... } 

który jest bezcelowe, jak trzeba powtarzać/powielać definicję obu operacji i nie będzie odwoływać się z interfejsu. IMHO robi to źle i powielam kod, którego staram się unikać.

Może to być również wskaźnik, że w jednej klasie jest zbyt duża odpowiedzialność za pochłonięcie 2 różnych przedmiotów (jeśli nie są połączone).

Jednak to, co robię i co można zrobić, to dodać wyraźny obiekt fabryczny stworzyć podłączonych odbiorników w następujący sposób:

interface ConsumerFactory { 
    Consumer<Apple> createAppleConsumer(); 
    Consumer<Tomato> createTomatoConsumer(); 
} 

Jeśli w rzeczywistości te typy są naprawdę połączony (związane) potem polecam utworzyć wdrożenie w taki sposób:

class TwoTypesConsumerFactory { 

    // shared objects goes here 

    private class TomatoConsumer implements Consumer<Tomato> { 
     public void consume(Tomato tomato) { 
      // you can access shared objects here 
     } 
    } 

    private class AppleConsumer implements Consumer<Apple> { 
     public void consume(Apple apple) { 
      // you can access shared objects here 
     } 
    } 


    // It is really important to return generic Consumer<Apple> here 
    // instead of AppleConsumer. The classes should be rather private. 
    public Consumer<Apple> createAppleConsumer() { 
     return new AppleConsumer(); 
    } 

    // ...and the same here 
    public Consumer<Tomato> createTomatoConsumer() { 
     return new TomatoConsumer(); 
    } 
} 

zaletą jest to, że klasa fabryka wie obie implementacje, istnieje wspólny stan (jeśli to konieczne) i można powrócić bardziej powiązanych z konsumentów w razie potrzeby. Nie ma powtarzania deklaracji metody konsumowania, która nie pochodzi z interfejsu.

Należy pamiętać, że każdy konsument może być niezależną (wciąż prywatną) klasą, jeśli nie są całkowicie spokrewnieni.

Wadą tego rozwiązania jest to klasa wyższa złożoność (nawet jeśli może to być jeden plik Java) i dostęp do spożywania metoda trzeba jeszcze jedną rozmowę więc zamiast:

twoTypesConsumer.consume(apple) 
twoTypesConsumer.consume(tomato) 

masz:

twoTypesConsumerFactory.createAppleConsumer().consume(apple); 
twoTypesConsumerFactory.createTomatoConsumer().consume(tomato); 

Podsumowując można określić 2 konsumentom generycznych w jednej klasie najwyższego poziomu za pomocą 2 klas wewnętrznych, ale w przypadku wywoływania trzeba się najpierw odniesienie do odpowiednich realizacji konsument, ponieważ nie może to być po prostu jeden obiekt konsumencki.

Powiązane problemy