일반 인터페이스가 있습니다
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);
}
기능적으로이 솔루션은 괜찮습니다. 그것은 장황하고 추악합니다.
어떤 아이디어?
답변
캡슐화를 고려하십시오.
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);
}
}
답변
유형 삭제 때문에 동일한 유형의 인터페이스를 사용하여 동일한 인터페이스를 두 번 구현할 수 없습니다.
답변
다음을 기반으로 가능한 솔루션이 있습니다. Steve McLeod의 .
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>
객체에 대한 요구는 이를 매개 변수로 예상하는 다른 방법에서 비롯됩니다. 상태를 공유하려면 두 클래스를 모두 구현해야합니다.
Steve의 아이디어는 각각 다른 제네릭 형식을 구현하는 두 개의 내부 클래스를 사용하는 것이 었습니다.
이 버전은 컨슈머 인터페이스를 구현하는 오브젝트에 대한 게터를 추가 한 후이를 기대하는 다른 메소드로 전달할 수 있습니다.
답변
최소한 다음과 같은 작업을 수행하여 디스패치 구현을 약간 개선 할 수 있습니다.
public class TwoTypesConsumer implements Consumer<Fruit> {
토마토와 사과의 조상 인 과일.
답변
그냥 넘어 졌어요 방금 동일한 문제가 발생했지만 다른 방식으로 해결했습니다. 이와 같이 새로운 인터페이스를 만들었습니다.
public interface TwoTypesConsumer<A,B> extends Consumer<A>{
public void consume(B b);
}
불행하게도,이 같은 것으로 간주됩니다 Consumer<A>
및 NOT으로 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 { ... }
두 작업의 정의를 반복 / 복제해야하며 인터페이스에서 참조되지 않으므로 의미가 없습니다. 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 개의 일반 소비자를 정의 할 수 있지만 호출하는 경우 먼저 하나의 소비자 개체 일 수 없으므로 적절한 구현 소비자에 대한 참조를 가져와야합니다.
답변
기능적 스타일에서는 인터페이스를 구현하지 않고이 작업을 수행하는 것이 매우 쉽고 컴파일 시간 유형 검사도 수행합니다.
엔티티를 소비하는 기능 인터페이스
@FunctionalInterface
public interface Consumer<E> {
void consume(E e);
}
엔티티를 적절하게 처리하고 소비하는 관리자
public class Manager {
public <E> void process(Consumer<E> consumer, E entity) {
consumer.consume(entity);
}
public void consume(Tomato t) {
// Consume Tomato
}
public void consume(Apple a) {
// Consume Apple
}
public void test() {
process(this::consume, new Tomato());
process(this::consume, new Apple());
}
}