2009-08-19 1 views
129

У меня есть универсальный интерфейсКак создать класс Java, который реализует один интерфейс с двумя типичными типами?

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

У меня есть класс, который потребляет два типа объектов, так что я хотел бы сделать что-то вроде:

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

Видимо, я не могу этого сделать.

Я могу, конечно, осуществить отправку самостоятельно, например.

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

Но я ищу решение для проверки и диспетчеризации времени компиляции, которое предоставляют дженерики.

Лучшее решение, о котором я могу думать, это определить отдельные интерфейсы, например.

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

Функционально это решение в порядке, я думаю. Это просто многословие и уродство.

Любые идеи?

+0

Зачем вам нужны два общих интерфейса одного и того же базового типа? – akarnokd

+5

Из-за стирания типа вы не можете этого сделать. Храните его в двух разных классах, которые реализуют потребитель. Делает более маленькие классы, но сохраняет общий код (не используйте принятый ответ, он нарушает всю концепцию ... вы не можете рассматривать TwoTypesConsumer как потребитель, который является ПЛОХОЙ). –

ответ

67

Рассмотрим инкапсуляция:

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) { ..... } 
    } 
} 

При создании этих статических внутренних классов беспокоит вас, вы можете использовать анонимные классы:

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

как-то выглядит похоже на дублирование кода ... Я столкнулся с той же проблемой и не нашел другого решения, которое выглядит чистым. –

+78

Но 'TwoTypesConsumer' выполняет * нет * контракты, так что в чем смысл? Он не может быть передан методу, который хочет использовать любой тип «Потребитель». Вся идея потребителя двух типов заключалась бы в том, что вы можете дать его методу, который хочет томатного потребителя, а также методу, который нужен потребителю Apple. Здесь у нас нет. –

+0

@JeffAxelrod Я бы сделал внутренние классы нестационарными, чтобы они имели доступ к приложенному экземпляру «TwoTypesConsumer», если это необходимо, и затем вы можете передать 'twoTypesConsumer.getAppleConsumer()' методу, который хочет потребителя Apple. Другим вариантом было бы добавить методы, похожие на 'addConsumer (Producer производитель)' to TwoTypesConsumer. – herman

30

Из-за стирания типа вы не можете реализовать один и тот же интерфейс дважды (с разными параметрами).

+5

Я вижу, как это проблема ... вопрос в том, какой из лучших (наиболее эффективный, безопасный, элегантный) способ обойти эту проблему. – daphshez

+1

Не вдаваясь в бизнес-логику, что-то здесь «пахнет» как шаблон посетителя. –

7

По крайней мере, вы можете сделать небольшое улучшение в вашей реализации отправки делать что-то вроде следующего:

public class TwoTypesConsumer implements Consumer<Fruit> { 

Плод предок помидоров и Apple.

+10

Спасибо, но независимо от того, что говорят профессионалы, я не считаю Томато плодом.К сожалению, нет общего базового класса, кроме Object. – daphshez

+1

Вы всегда можете создать базовый класс под названием: AppleOrTomato;) –

+1

Лучше добавьте Фрукты, которые делегируют либо Apple, либо Tomato. –

10

Вот возможное решение, основанное на 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); 
      } 
     } 
    } 
} 

Неявное требование вопроса было Consumer<Tomato> и Consumer<Apple> объектов, которые делят состояние. Необходимость в объектах Consumer<Tomato>, Consumer<Apple> исходит из других методов, которые ожидают их как параметров. Мне нужен один класс, чтобы реализовать их как для совместного использования.

Идея Стива заключалась в том, чтобы использовать два внутренних класса, каждый из которых реализует другой общий тип.

В этой версии добавлены геттеры для объектов, реализующих пользовательский интерфейс, которые затем могут передаваться другим ожидающим их методам.

+1

Если кто-то использует это: стоит хранить экземпляры 'Consumer <*>' в полях экземпляра, если вызов 'get * Consumer' вызывается часто. – TWiStErRob

3

просто наткнулся на это. Это просто случилось, что у меня была такая же проблема, но я решил ее по-другому: Я просто создал новый интерфейс, как этот

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

, к сожалению, это рассматривается как Consumer<A> и НЕ как Consumer<B> против всех логики ,Таким образом, вы должны создать небольшой адаптер для второго потребителя, как это внутри класса

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){ //... 
    } 
} 

если Consumer<A> требуется, вы можете просто передать this, и если Consumer<B> нужно просто пройти consumerAdapter

+0

[Daphna] (http://stackoverflow.com/a/1298751/253468) Ответ такой же, но более чистый и менее запутанный. – TWiStErRob

1

Вы не можете сделайте это прямо в одном классе, так как определение класса ниже не может быть скомпилировано из-за стирания общих типов и декларации о двойном интерфейсе.

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

Любое другое решение для упаковки такой же потреблять операции в одном классе требует, чтобы определить свой класс как:

class TwoTypesConsumer { ... } 

, который не имеет смысла, как вам нужно повторить/дублировать определение обеих операций и они на интерфейс не будет ссылаться. IMHO делает это плохое маленькое дублирование кода, которое я пытаюсь избежать.

Это может быть признаком того, что в одном классе слишком много ответственности, чтобы потреблять 2 разных объекта (если они не связаны).

Однако то, что я делаю, и то, что вы можете сделать, это добавить явный фабричный объект для создания подключенных потребителей следующим образом:

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

Если в действительности эти типы действительно связаны (связанно), то я рекомендовал бы создать реализацию таким образом:

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

преимущества заключается в том, что фабрика класс знает, как реализации, существует общее состояние (при необходимости), и вы можете вернуть более Вкупе потребитель, если это необходимо. Нет повторения декларации метода потребления, которые не выводятся из интерфейса.

Обратите внимание, что каждый потребитель может быть независимым (все еще закрытым) классом, если он не полностью связан.

Недостатком этого решения является более высокого класса сложности (даже если это может быть один Java файл а) и для доступа к потреблению метод вам нужен еще один вызов, так вместо:

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

у вас есть:

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

Резюмируя можно определить 2 родовых потребителей в одном классе верхнего уровня с использованием 2 внутренних классов, но в случае вызова вам необходимо получить первую ссылку на соответствующие реализации потребитель, поскольку это не может быть просто одним потребительским объектом.