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

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

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

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

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

1 ответ

Вы можете сделать enumс не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 то, что будет оптимизировано прочь; так что эта проверка не влияет на производительность.

Другие вопросы по тегам