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