두 가지 일반 유형으로 하나의 인터페이스를 구현하는 Java 클래스를 작성하는 방법은 무엇입니까?


164

일반 인터페이스가 있습니다

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

기능적으로이 솔루션은 괜찮습니다. 그것은 장황하고 추악합니다.

어떤 아이디어?


동일한 기본 유형의 두 개의 일반 인터페이스가 필요한 이유는 무엇입니까?
akarnokd

6
유형 삭제로 인해 그렇게 할 수 없습니다. 소비자를 구현하는 두 개의 다른 클래스를 유지하십시오. 더 작은 클래스를 만들지 만 코드를 일반적으로 유지합니다 (허용 된 답변을 사용하지 말고 전체 개념을 깨뜨립니다. TwoTypesConsumer를 소비자 인 BAD로 취급 할 수 없습니다).
Lewis Diamond

기능적 스타일 impl에 대해이를 확인하십시오.- stackoverflow.com
a

답변:


78

캡슐화를 고려하십시오.

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

2
어떻게 든 코드 중복과 같은 것처럼 보입니다 ... 같은 문제가 발생하여 깨끗해 보이는 다른 솔루션을 찾지 못했습니다.
bln-tom

109
그러나 계약을 TwoTypesConsumer이행 하지 않으므로 요점은 무엇입니까? 의 유형 중 하나를 원하는 메소드에는 전달할 수 없습니다 Consumer. 두 가지 유형의 소비자에 대한 전체 아이디어는 토마토 소비자를 원하는 방법뿐만 아니라 사과 소비자를 원하는 방법으로 그것을 줄 수 있다는 것입니다. 여기에는 둘 다 없습니다.
Jeff Axelrod

@JeffAxelrod 나는 내부 클래스를 비 정적으로 만들면 TwoTypesConsumer필요한 경우 둘러싼 인스턴스에 액세스 할 수 twoTypesConsumer.getAppleConsumer()있으며 사과 소비자를 원하는 메소드로 전달할 수 있습니다 . 다른 옵션은 addConsumer(Producer<Apple> producer)TwoTypesConsumer 와 유사한 메소드를 추가하는 것 입니다.
herman September

인터페이스를 제어 할 수없는 경우 (예 : cxf / rs ExceptionMapper) 작동하지 않습니다 ...
vikingsteve

17
내가 말할 것이다 : 이것은 Java 의 결함 입니다. 구현이 다른 인수를 사용하는 경우 동일한 인터페이스의 여러 구현을 허용해서는 안되는 이유는 없습니다.
gromit190

41

유형 삭제 때문에 동일한 유형의 인터페이스를 사용하여 동일한 인터페이스를 두 번 구현할 수 없습니다.


6
나는 그것이 어떻게 문제인지 알 수 있습니다 ... 그렇다면 문제는이 문제를 우회하는 가장 좋은 (가장 효율적이고 안전하며 우아한) 방법입니다.
daphshez

2
비즈니스 로직에 들어 가지 않고 여기에서 무언가가 방문자 패턴과 같은 '냄새가납니다'.
Shimi Bandiel

12

다음을 기반으로 가능한 솔루션이 있습니다. 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의 아이디어는 각각 다른 제네릭 형식을 구현하는 두 개의 내부 클래스를 사용하는 것이 었습니다.

이 버전은 컨슈머 인터페이스를 구현하는 오브젝트에 대한 게터를 추가 한 후이를 기대하는 다른 메소드로 전달할 수 있습니다.


2
누구나 이것을 사용하는 경우 : 자주 호출되는 Consumer<*>경우 인스턴스 필드에 인스턴스를 저장하는 get*Consumer것이 좋습니다.
TWiStErRob

7

최소한 다음과 같은 작업을 수행하여 디스패치 구현을 약간 개선 할 수 있습니다.

public class TwoTypesConsumer implements Consumer<Fruit> {

토마토와 사과의 조상 인 과일.


14
고마워하지만 프로가 말한대로 토마토는 과일로 간주하지 않습니다. 불행히도 Object 이외의 공통 기본 클래스는 없습니다.
daphshez

2
) AppleOrTomato : 당신은 항상라는 기본 클래스를 만들 수 있습니다
시므이 Bandiel

1
더 나은 방법은 애플이나 토마토에 위임 된 과일을 추가하는 것입니다.
Tom Hawtin-tackline

@Tom : 내가 말하는 것을 오해하지 않는 한, Fruit은 Apple 또는 Tomato에 위임 할 수 있기 때문에 Fruit은 Apple과 Tomato 모두에 슈퍼 클래스 필드를 가져야하기 때문에 문제를 앞으로 나아갑니다. 위임하는 객체를 참조하십시오.
Buhb

1
이는 TwoTypesConsumer가 모든 유형의 과일, 현재 구현되어 있고 향후 구현할 수있는 과일을 소비 할 수 있음을 의미합니다.
Tom Gillen

3

그냥 넘어 졌어요 방금 동일한 문제가 발생했지만 다른 방식으로 해결했습니다. 이와 같이 새로운 인터페이스를 만들었습니다.

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


Daphna 의 대답은 동일하지만 깨끗하고 덜 복잡합니다.
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 개의 일반 소비자를 정의 할 수 있지만 호출하는 경우 먼저 하나의 소비자 개체 일 수 없으므로 적절한 구현 소비자에 대한 참조를 가져와야합니다.


1

기능적 스타일에서는 인터페이스를 구현하지 않고이 작업을 수행하는 것이 매우 쉽고 컴파일 시간 유형 검사도 수행합니다.

엔티티를 소비하는 기능 인터페이스

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

0

더 많은 클래스를 사용하지 않는 또 다른 대안. (java8 +를 사용하는 예)

// Mappable.java
public interface Mappable<M> {
    M mapTo(M mappableEntity);
}

// TwoMappables.java
public interface TwoMappables {
    default Mappable<A> mapableA() {
         return new MappableA();
    }

    default Mappable<B> mapableB() {
         return new MappableB();
    }

    class MappableA implements Mappable<A> {}
    class MappableB implements Mappable<B> {}
}

// Something.java
public class Something implements TwoMappables {
    // ... business logic ...
    mapableA().mapTo(A);
    mapableB().mapTo(B);
}

0

오래된 질문에 대한 답변으로 유감이지만 정말 좋아합니다! 이 옵션을 사용해보십시오 :

public class MegaConsumer implements Consumer<Object> {

  Map<Class, Consumer> consumersMap = new HashMap<>();
  Consumer<Object> baseConsumer = getConsumerFor(Object.class);

  public static void main(String[] args) {
    MegaConsumer megaConsumer = new MegaConsumer();
    
    //You can load your customed consumers
    megaConsumer.loadConsumerInMapFor(Tomato.class);
    megaConsumer.consumersMap.put(Apple.class, new Consumer<Apple>() {
        @Override
        public void consume(Apple e) {
            System.out.println("I eat an " + e.getClass().getSimpleName());
        }
    });
    
    //You can consume whatever
    megaConsumer.consume(new Tomato());
    megaConsumer.consume(new Apple());
    megaConsumer.consume("Other class");
  }

  @Override
  public void consume(Object e) {
    Consumer consumer = consumersMap.get(e.getClass());
    if(consumer == null) // No custom consumer found
      consumer = baseConsumer;// Consuming with the default Consumer<Object>
    consumer.consume(e);
  }

  private static <T> Consumer<T> getConsumerFor(Class<T> someClass){
    return t -> System.out.println(t.getClass().getSimpleName() + " consumed!");
  }

  private <T> Consumer<T> loadConsumerInMapFor(Class<T> someClass){
    return consumersMap.put(someClass, getConsumerFor(someClass));
  }
}

나는 그것이 당신이 찾고있는 것이라고 생각합니다.

이 출력을 얻습니다.

토마토 소비!

나는 사과를 먹는다

문자열이 소비되었습니다!


질문 : "하지만 컴파일 타임 유형 검사를 찾고 있습니다 ..."
aeracode

@aeracode OP가 원하는 것을 수행하는 옵션이 없습니다. 타입 삭제는 타입 변수가 다른 동일한 인터페이스를 두 번 구현할 수 없습니다. 나는 당신에게 다른 길을 주려고 노력합니다. 물론 당신은 onbject를 소비하기 위해 이전에 허용 된 유형을 확인할 수 있습니다.
Awes0meM4n
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.