views:

517

answers:

4

I have a generic interface

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

I have a class that consumes two types of objects, so I would like to do something like:

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

Apparently I can't do that.

I can of course implement the dispatch myself, e.g.

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

But I am looking for the compile-time type-checking and dispatching solution that generics provide.

The best solution I can think of is to define separate interfaces, e.g.

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

Functionally, this solution is OK, I think. It's just verbose and ugly.

Any ideas?

+12  A: 

Because of type erasure you can't implement the same interface twice (with different type parameters).

Shimi Bandiel
I can see how it's a problem... the question is then what's the best (most efficient, safe, elegant) way to bypass this problem.
Daphna Shezaf
Without going into the business logic, something here 'smells' like the Visitor pattern.
Shimi Bandiel
+1  A: 

At least, you can make a small improvement to your implementation of dispatch by doing something like the following:

public class TwoTypesConsumer implements Consumer<Fruit> {

Fruit being an ancestor of Tomato and Apple.

Buhb
Thanks, but whatever the pros says, I don't regard Tomato as fruit. Unfortunately there's no common base class other than Object.
Daphna Shezaf
You can always create a base class called: AppleOrTomato ;)
Shimi Bandiel
Better, add a Fruit that delegates to either Apple or Tomato.
Tom Hawtin - tackline
@Tom: Unless i'm misunderstanding what you're saying, your suggestion only pushes the problem forward, since, for Fruit to be able to delegate to either Apple or Tomato, Fruit must have a field of a superclass to both Apple and Tomato referring to the object it delegates to.
Buhb
+7  A: 

Consider encapsulation:

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

}

If creating these static inner classes bothers you, you can use anonymous classes:

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
+1 - This I rather like.
banjollity
+1  A: 

Here's a possible solution based on 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);
        }
   }
}

The implicit requirement of the question was Consumer and Consumer objects that share state. The need for Consumer,Consumer objects comes from other methods that expect these as parameters. I need one class the implement them both in order to share state.

Steve's idea was to use two inner classes, each implementing a different generic type.

This version adds getters for the objects that implement the Consumer interface, which can then be passed to other methods expecting them.

Daphna Shezaf