Jak stworzyć klasę Javy, która implementuje jeden interfejs z dwoma typami generycznymi?

Mam ogólny interfejs

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

Mam klasę, która zużywa dwa rodzaje 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) { ...... }
}
Najwyraźniej nie mogę tego zrobić.

Mogę oczywiście sam zrealizować wysyłkę, 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 rozwiązania do sprawdzania typu w czasie kompilacji i wysyłania, które zapewniają generyki.

Najlepszym rozwiązaniem, jakie przychodzi mi do głowy, jest zdefiniowanie oddzielnych interfejsów, np.

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

Funkcjonalnie To rozwiązanie chyba w porządku. To tylko gadatliwe i brzydkie.

Jakieś pomysły?
Author: daphshez, 2009-08-19

6 answers

Rozważmy enkapsulację:

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 tworzenie tych statycznych klas wewnętrznych przeszkadza ci, możesz użyć klas anonimowych:

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);
    }
}
 68
Author: Steve McLeod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-10 01:11:32

Z powodu kasowania typu nie można zaimplementować tego samego interfejsu dwa razy (z różnymi parametrami typu).

 31
Author: Shimi Bandiel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 05:48:57

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);
            }
        }
    }
}

Dorozumianym wymogiem pytania były Consumer<Tomato> i Consumer<Apple> obiekty, które dzielą stan. Potrzeba obiektów Consumer<Tomato>, Consumer<Apple> pochodzi z innych metod, które oczekują ich jako parametrów. Potrzebuję jednej klasy, aby zaimplementować je obie, aby dzielić stan.

Pomysł Steve ' a polegał na użyciu dwóch klas wewnętrznych, z których każda implementuje inny typ generyczny.

Ta wersja dodaje gettery dla obiektów, które implementują interfejs użytkownika, który następnie może być przekazywany innym oczekiwanym przez niego metodom.

 9
Author: daphshez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:22

Możesz przynajmniej poprawić swoją implementację wysyłania, wykonując coś w stylu:

public class TwoTypesConsumer implements Consumer<Fruit> {
Owoc będący przodkiem pomidora i jabłka.
 7
Author: Buhb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 06:13:14

Natknąłem się na to. Tak się złożyło, że miałem ten sam Problem, ale rozwiązałem go w inny sposób: Właśnie stworzyłem nowy interfejs, taki jak ten

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

Niestety, jest to uważane za Consumer<A>, a nie za Consumer<B> wbrew wszelkiej logice. Więc musisz stworzyć mały Adapter dla drugiego konsumenta, jak ten wewnątrz swojej 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 potrzebne jest Consumer<A>, możesz po prostu przekazać this, a jeśli Consumer<B> jest potrzebne po prostu przekazać consumerAdapter

 3
Author: Rafael T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-22 23:21:16

Nie można tego zrobić bezpośrednio w jednej klasie, ponieważ poniższa definicja klasy nie może być skompilowana z powodu usunięcia typów ogólnych i zduplikowanej deklaracji interfejsu.

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

Każde inne rozwiązanie do pakowania tych samych operacji w jednej klasie wymaga zdefiniowania klasy jako:

class TwoTypesConsumer { ... }

Co jest bezcelowe, ponieważ musisz powtórzyć / powielić definicję obu operacji i nie będą one odwoływane z interfejsu. IMHO robienie tego to kiepskie małe i duplikowanie kodu które Staram się unikać.

Może to być również wskaźnikiem, że w jednej klasie jest zbyt wiele odpowiedzialności, aby konsumować 2 różne obiekty (jeśli nie są one połączone).

Jednak to, co robię i co możesz zrobić, to dodać explicit Factory object, aby utworzyć połączonych konsumentów w następujący sposób:

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

Jeśli w rzeczywistości te typy są naprawdę sprzężone (powiązane) to polecam stworzyć implementację 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 factory zna obie implementacje, istnieje wspólny stan (w razie potrzeby) i w razie potrzeby można zwrócić więcej sprzężonych konsumentów. Nie ma powtarzalnej deklaracji użycia metody, która nie pochodzi z interfejsu.

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

Minusem tego rozwiązania jest wyższa złożoność klasy (nawet jeśli może to być jeden plik java) i aby uzyskać dostęp do metody consume, potrzebujesz jeszcze jednego wywołania więc zamiast:

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

Masz:

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

Podsumowując, możesz zdefiniować 2 generycznych konsumentów w jednej klasie najwyższego poziomu, używając 2 wewnętrznych klas, ale w przypadku wywołania musisz najpierw uzyskać odniesienie do odpowiedniego implementującego konsumenta, ponieważ nie może to być po prostu jeden obiekt konsumencki.

 1
Author: kitarek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-29 09:06:32