Заставить детей использовать перечисления, определенные внутри себя

Допустим, у меня есть родительский абстрактный класс дрессировщика животных:

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

Я хочу, чтобы конкретные расширения родительского дрессировщика животных завершали обучение только для определенных им обучаемых. Итак, если у меня есть конкретный дрессировщик собак следующим образом:

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

С текущим определением DogTrainer я могу сделать только trainingComplete для параметров типа DogTrainer.Tricks. Но я хочу добиться, чтобы любой, кто создает конкретное Trainer, разрешал trainingComplete() использовать Trainables, которое он определяет внутри себя.

Другими словами, проблема с моим текущим дизайном заключается в том, что если бы у меня был другой тренер, это выглядело бы следующим образом:

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

Ничто не мешает кому-то дать определение еще одному дрессировщику румян, который пытается научить собаку полицейским трюкам:

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

Я хочу запретить это и разрешить расширяющему классу использовать ТОЛЬКО обучаемые, которые они сами указывают.

Как я могу это сделать?


person anishthecoder    schedule 17.10.2013    source источник
comment
Я думаю, что это также решается решением для моего: более раннего вопроса.   -  person anishthecoder    schedule 20.05.2017


Ответы (1)


Вы можете сделать enums отличным от public, но это не может быть обеспечено абстрактным базовым классом. В качестве альтернативы можно сделать Trainables универсальным, добавив параметр типа, который должен соответствовать классу Trainer. Это не принуждает enum быть внутренним классом (это невозможно), но для соответствующего подкласса тогда не может быть создан RogueTrainer.

Наложение ограничений на тип this внутри базового класса или интерфейса находится где-то между сложным и невозможным. Одним из широко известных примеров является интерфейс Comparable, который нельзя объявить таким образом, чтобы предотвратить реализацию, подобную class Foo implements Comparable<String>.

Один из способов обойти эту проблему — сделать ссылку Trainer параметром, например

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

Метод public static можно вызвать только с правильной комбинацией Trainer и Trainables. Метод trainingCompleteImpl может вызываться и переопределяться доверенными подклассами в одном пакете. Если вы этого не хотите, вы можете встроить код метода и полностью удалить метод экземпляра.

_

Альтернативой является создание параметра типа для Trainer и обеспечение соответствия между параметром и this во время выполнения:

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

Таким образом, несоответствующая Trainer реализация selfReference() не может быть реализована как return this;, которую можно легко обнаружить. Для соответствующей реализации JVM встроит метод selfReference и увидит this==this, который затем будет оптимизирован; поэтому эта проверка не влияет на производительность.

person Holger    schedule 17.10.2013
comment
Итак, Trainables следует определять как public interface Trainables<T extends Trainer>? Как я могу обеспечить это в родительском Trainer - person anishthecoder; 17.10.2013
comment
я расширил ответ - person Holger; 18.10.2013