vendredi 11 octobre 2019

How to design code around disjoint types that share similar behavior?

Premise

Existing codebase (not likely to change):

public interface Thing {
  void doSomething();
}

And with the implementations such as (ThingA, ThingB, ThingN, etc):

public final class ThingA implements Thing {

  private final ThingAInput input;

  public ThingA(ThingAInput input) {
    this.input = input;
  }

  @Override
  public void doSomething() { ... }
}

ThingAInput, ThingBInput, and ThingNInput are NOT related at all (disjoint types).

Problem

Input objects can be very complex and I want users to create a class that encapsulates their own business logic. I explored two approaches, but they were both clunky and that leads me to think if I am approaching the problem incorrectly and there is a better way to use Java.

Option 1

I thought about defining a generic interface:

public interface ThingInput<T> {
  T getInput();
}

User can then create:

public final class MyThingInput<ThingBInput> {

  private final ThingDep thingDep;

  @Inject
  MyThingInput(ThingDep thingDep) {
    this.thingDep = thingDep;
  }

  @Override
  public ThingBInput getInput() {
    return createThingAInput(thingDep);
  }

  // ... very complex business logic ...
  private static ThingAInput createThingAInput(ThingDep dep) {
    // returns a ThingAInput
  }
}

then use a ThingFactory to create the correct instance based on type. But I am unable to say ThingInput<ThingAInput | ThingBInput> and enforcing this behavior requires runtime checks.

Option 2

I could use inheritance on Thing directly:

public abstract class AbstractThing implements Thing {

  protected final Thing thingImpl;

  public AbstractThing(ThingAInput input) {
    this.thingImpl = new ThingA(input);
  }

  public AbstractThing(ThingBInput input) {
    this.thingImpl = new ThingB(input);
  }

  // Proxies doSomething to the underlying impl.
  @Override
  public void doSomething() {
    return ThingImpl.doSomething();
  }
}

and users can create:

public final MyThingA extends AbstractThing {

  @Inject
  MyThingA(ThingDep thingDep) {
    super(createThingAInput(thingDep));
  }

  // ... very complex business logic ...
  private static ThingAInput createThingAInput(ThingDep dep) {
    // returns a ThingAInput
  }
}

Aucun commentaire:

Enregistrer un commentaire