Как сделать 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);
}


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



какие идеи?

598   6  

6 ответов:

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

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

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

вот возможное решение на основе Стив Маклеод один:

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> объекты поступает от других методов, которые ожидают их в качестве параметров. Мне нужен один класс реализации их обоих для того, чтобы разделить состояние.

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

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

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

public class TwoTypesConsumer implements Consumer<Fruit> {

плод, являющийся предком помидора и Яблока.

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

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

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

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

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

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

class TwoTypesConsumer { ... }

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

это может быть также показателем того, что в одном классе слишком много ответственности, чтобы потреблять 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();
    }
}

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

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

недостатком этого решения является более высокая сложность класса (даже если это может быть a one java file) и для доступа к методу consume вам нужен еще один вызов so вместо:

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

у вас есть:

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

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

Comments

    Ничего не найдено.