1

Let's say I've got a parent abstract animal trainer class:

public abstract class Trainer 
  <A extends Animal, 
   E extends Enum<E> & Trainables>{
    protected EnumSet<E> completed;
    public void trainingComplete(E trainable){
      trainingComplete.add(trainable);
    }

I want concrete extensions of the parent animal trainer to complete training for only the trainables defined by it. So if I have a concrete Dog Trainer as follows:

public class DogTrainer extends Trainer<Dog, DogTrainer.Tricks>{
  public enum Tricks implements Trainables {
    FETCH, GROWL, SIT, HEEL;
  }
}

With the current definition of DogTrainer I can only do trainingComplete for parameters of the DogTrainer.Tricks type. But I want to enforce that anyone who creates a concrete Trainer should allow trainingComplete() for Trainables that it defines within itself.

In other words, the problem with my current design is, if I had another trainer as follows:

public class PoliceDogTrainer extends Trainer<Dog, PoliceDogTrainer.Tricks>{
  public enum Tricks implements Trainables {
     FIND_DRUGS, FIND_BOMB, FIND_BODY;
  }
}

There is nothing preventing someone from defining another rouge trainer that tries to teach the dog, police tricks:

public class RougeTrainer extends Trainer<Dog, PoliceDogTrainer.Tricks>{
 ...
}

I want to prohibit this and allow extending class to use ONLY Trainables they themselves specify.

How can I do that?

anishthecoder
  • 940
  • 6
  • 20
  • I think this is also solved by the solution for my : [earlier question](http://stackoverflow.com/questions/19435006/force-children-to-use-enums-defined-within-themselves). – anishthecoder May 20 '17 at 20:45

1 Answers1

1

You can make the enums non-public but that cannot be enforced by the abstract base class. An alternative is to make Trainables generic by adding a type parameter which must match the Trainer class. This does not enforce the enum to be an inner class (that’s impossible) but for a conforming sub class, no RogueTrainer can be created then.

Enforcing constraints on the type of this inside the base class or interface lies somewhere between tricky and impossible. One commonly known example is the Comparable interface which cannot be declared in a way to prevent implementations like class Foo implements Comparable<String>.

One way to circumvent this problem is to make the Trainer reference a parameter, e.g.

public interface Trainables<T extends Trainer<?,? extends Trainables<T>>>
…
public abstract class Trainer 
  <A extends Animal,
   E extends Enum<E> & Trainables<? extends Trainer<A,E>>> {

  protected EnumSet<E> completed;

  void trainingCompleteImpl(E trainable) {
    completed.add(trainable);
  }

  public static <A extends Animal, T extends Trainer<A,E>,
    E extends Enum<E> & Trainables<T>> void trainingComplete(T t, E trainable) {
    t.trainingCompleteImpl(trainable);
  }
}

public class PoliceDogTrainer
  extends Trainer<Dog, PoliceDogTrainer.Tricks> {

  public enum Tricks implements Trainables<PoliceDogTrainer> {
     FIND_DRUGS, FIND_BOMB, FIND_BODY;
  }
}

The public static method can only be invoked with the right combination of Trainer and Trainables. The trainingCompleteImpl method can be invoked and overridden by trusted subclasses within the same package. If you don’t want this you can inline the code of the method and remove the instance method completely.

_

An alternative is to create a type parameter for the Trainer and enforce a match between the parameter and this at runtime:

public interface Trainables<T extends Trainer<?,T,? extends Trainables<T>>>
…
public abstract class Trainer 
  <A extends Animal, T extends Trainer<A,T,E>,
   E extends Enum<E> & Trainables<T>> {

  protected EnumSet<E> completed;

  /** sub-classes should implements this as {@code return this}*/
  protected abstract T selfReference();

  void trainingComplete(E trainable) {
    if(selfReference()!=this) throw new IllegalStateException();
    completed.add(trainable);
  }
}
public class PoliceDogTrainer
  extends Trainer<Dog, PoliceDogTrainer, PoliceDogTrainer.Tricks> {

  public enum Tricks implements Trainables<PoliceDogTrainer> {
     FIND_DRUGS, FIND_BOMB, FIND_BODY;
  }

  @Override
  protected final PoliceDogTrainer selfReference()
  {
    return this;
  }
}

So, for a non-conforming Trainer implementation selfReference() cannot be implemented as return this; which can be detected easily. For a conforming implementation the JVM will inline the selfReference method and see this==this then which will be optimized away; so this check has no performance impact.

Holger
  • 285,553
  • 42
  • 434
  • 765