development

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

big-blog 2020. 6. 8. 07:57
반응형

두 가지 일반 유형으로 하나의 인터페이스를 구현하는 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);
}

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

어떤 아이디어?


캡슐화를 고려하십시오.

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

unfortunately, this is considered as Consumer<A> and NOT as Consumer<B> against all Logic. So you have to create a small Adapter for the second consumer like this inside your class

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

if a Consumer<A> is needed, you can simply pass this, and if Consumer<B> is needed just pass consumerAdapter


You cannot do this directly in one class as the class definition below cannot be compiled due to erasure of generic types and duplicate interface declaration.

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

Any other solution for packing the same consume operations in one class requires to define your class as:

class TwoTypesConsumer { ... }

which is pointless as you need to repeat/duplicate the definition of both operations and they won't be referenced from interface. IMHO doing this is a bad small and code duplication which I'm trying to avoid.

This might be an indicator also that there is too much responsibility in one class to consume 2 different objects (if they aren't coupled).

However what I'm doing and what you can do is to add explicit factory object to create connected consumers in the following way:

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

If in reality those types are really coupled (related) then I would recommend to create an implementation in such way:

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

The advantage is that the factory class knows both implementations, there is a shared state (if needed) and you can return more coupled consumers if needed. There is no repeating consume method declaration which aren't derived from interface.

Please note that each consumer might be independent (still private) class if they aren't completely related.

The downside of that solution is a higher class complexity (even if this can be a one java file) and to access consume method you need one more call so instead of:

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

you have:

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

To summarize you can define 2 generic consumers in one top-level class using 2 inner classes but in case of calling you need to get first a reference to appropriate implementing consumer as this cannot be simply one consumer object.


Another alternative to avoid the use of more classes. (example using 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);
}

Sorry for answer old questions, but I really love it! Try this option:

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

I think that is what you are looking for.

You get this output:

Tomato consumed!

I eat an Apple

String consumed!

참고URL : https://stackoverflow.com/questions/1297972/how-to-make-a-java-class-that-implements-one-interface-with-two-generic-types

반응형