Search code examples
javainterfacejava-8abstractparameterized-types

Java Optional Parametrized Interfaces


I have an interface like so:

public interface Foo<T> {
    default void doStuff(T val1, T val2) {}

    default void doStuff(T val) {}
}

The idea behind the interface being you can implement either one or the other methods. So then I have an abstract class like so:

abstract public class AbstractFoo<T> implements Foo<T> {
    //Bunch of Stuff
}

And finally an implementation like so:

public class AwesomeOne extends AbstractFoo<String> {
    @Override
    public void doStuff(String val) {
        //Do Stuff
    }
}

Here's an example of another implementation:

public class AwesomeTwo extends AbstractFoo<String> {
    @Override
    public void doStuff(String val1, String val2) {
        //Do Stuff
    }
}

This works just fine. Now, I want to be able to specify 2 different types for the doStuff(val1, val2) method. So I would like to be able to do something like this:

public class AwesomeThree extends AbstractFoo<String, Double> {
    @Override
    public void doStuff(String val1, Double val2) {
        //Do Stuff
    }
}

This is how my interface would change:

public interface Foo<S, T> {
    default void doStuff(S val1, T val2) {}

    default void doStuff(T val) {}
}

But I am having difficulty getting the abstract class to work with this. Because while I can change my abstract class to this:

abstract public class AbstractFoo<S, T> implements Foo<S, T> {
    //Bunch of Stuff
}

That would change implementation classes like so:

public class AwesomeOne extends AbstractFoo<Object, String> {
    @Override
    public void doStuff(String val) {
        //Do Stuff
    }
}

Which works but....that's not really a clean solution. Also it would cause me to refactor all the existing implementations.

Basically in plain English I want to say:

When implementing the Foo interface, you can implement either of the 2 methods specified in the interface. If you are implementing the single parameter method, doStuff(T val), you only need to specify 1 parametrized type. If, however, you are implementing the double parameter method, doStuff(S val1, T val2), you have to specify both parameterized types.

Is this possible in Java and if so what is the cleanest approach to this problem?


Solution

  • You are violating some of the OO principles, which is why the design doesn't feel clean.

    Your specification that an implementation must do "this or that" smells like a violation of the interface segregation principle to me. That's the "I" of the SOLID principles.

    So to fix this, we want to represent the operation with an abstraction:

    public interface StuffDoer {
      void run();
    }
    

    Cool. That's easy. So now you want a 1-arg stuff doer and a 2-arg stuff doer. But every StuffDoer must be able to run(). See? Just one mandatory method now.

    public abstract class OneArgStuffDoer<T1> implements StuffDoer {
      private final T1 arg;
      public OneArgStuffDoer(T1 arg) {
        this.arg = Objects.requireNonNull(arg);
      }
      public final void run() {
        run(arg);
      }
      abstract void run(T1 arg);
    }
    

    You can play a similar game with TwoArgStuffDoer. Now your API to implementations will be to extend either OneArgStuffDoer or TwoArgStuffDoer, and implement either run(T1 arg) from the former or run(T1 arg1, T2 arg2) from that latter.

    Here's what AwesomeOne would look like:

    public final class AwesomeOne extends OneArgStuffDoer<String> {
      @Override
      void run(String str) { // do your awesome stuff here }
    }
    

    Nice and clean! Right!?!

    Hope that helps!