"Устанавливает" определенного типа перечисления, но с дженериками
Скажем, у меня есть абстрактный класс
public abstract class Trainer<T extends Animal>{}
У меня есть определенные тренеры вроде:
public DogTrainer extends Trainer<Dog>{}
public HorseTrainer extends Trainer<Horse>{}
У каждого из этих "тренеров" есть набор фиксированных трюков, которые они могут обучить животному, и я бы хотел использовать Enums. Поэтому у меня есть интерфейс:
public interface TrainingActions<T extends Animal>{}
и в каждом из тренеров у меня есть Enum, который реализует этот интерфейс. Итак:
public DogTrainer extends Trainer<Dog>{
public enum Trainables implements TrainingActions<Dog>{
BARK, BITE, ROLLOVER, FETCH;
}
}
public HorseTrainer extends Trainer<Horse>{
public enum Trainables implements TrainingActions<Horse>{
JUMP, TROT, REARUP;
}
}
Теперь в каждом из классов Тренера я хотел бы, чтобы метод сказал "trainingComplete", который берет одну из Enums в качестве входных данных и сохраняет ее в наборе. Так
public DogTrainer extends Trainer<Dog>{
public enum Trainables implements TrainingActions<Dog>{
BARK, BITE, ROLLOVER, FETCH;
}
public Set<Trainables> completed = new HashSet<Trainables>();
public void trainingComplete(Trainables t){completed.add(t);}
}
Однако вместо определения "завершенного" набора в каждом из конкретных тренеров и метода "trainingComplete" в каждом из тренеров мне бы хотелось что-то в родительском классе "Тренеры", который может быть принудительно включен в Enum... Так что это странное сочетание Enums и generics.
Возможно ли это?
Ответы
Ответ 1
Чтобы указать привязку к интерфейсу и что это перечисление, вам нужно общее пересечение, которое выглядит следующим образом:
class MyClass<T extends Enum<T> & SomeInterface> {}
Обратите внимание, что при пересечении класса и интерфейса (-ов) класс должен появиться перед интерфейсом (-ами).
В этом случае дженерики Kung Fu, которые вы хотите, на один уровень более сложны, потому что интерфейс перечислимого объекта TrainingActions сам должен ссылаться на тип животного.
class Trainer<A extends Animal, T extends Enum<T> & TrainingActions<A>> {}
Полный рабочий пример, основанный на вашем опубликованном коде, который компилируется:
public class Animal {}
public interface TrainingActions<T extends Animal> {}
/**
* A trainer that can teach an animal a suitable set of tricks
* @param <A> The type of Animal
* @param <T> The enum of TrainingActions that can be taught to the specified Animal
*/
public abstract class Trainer<A extends Animal, T extends Enum<T> & TrainingActions<A>> {
private Set<T> completed = new HashSet<T>();
public void trainingComplete(T t) {
completed.add(t);
}
}
public class Dog extends Animal {};
public class DogTrainer extends Trainer<Dog, DogTrainer.Trainables> {
public enum Trainables implements TrainingActions<Dog> {
BARK, BITE, ROLLOVER, FETCH;
}
}
Но я хотел бы сделать еще один шаг и определить несколько TrainingActions
перечислений в классе конкретного Animal
, к которому они применяются:
public class Dog extends Animal {
public enum BasicTrainables implements TrainingActions<Dog> {
SIT, COME, STAY, HEEL;
}
public enum IntermediateTrainables implements TrainingActions<Dog> {
BARK, BITE, ROLLOVER, FETCH;
}
public enum AdvacedTrainables implements TrainingActions<Dog> {
SNIFF_DRUGS, FIND_PERSON, ATTACK, GUARD;
}
};
public class PuppyTrainer extends Trainer<Dog, Dog.BasicTrainables> {}
public class ObedienceTrainer extends Trainer<Dog, Dog.IntermediateTrainables> {}
public class PoliceTrainer extends Trainer<Dog, Dog.AdvacedTrainables> {}
Ответ 2
Несмотря на анализ дизайна, я думаю, что самая ваша ситуация (ближайшая к вашему уже созданному дизайну):
-
DogTrainer.Trainables
TrainingActions<Dog>
- ваш
HorseTrainer.Trainables
- TrainingActions<Horse>
Как правило, ваш Trainables
уже является TrainingActions<T extends Animal>
поэтому вы можете просто предоставить Set<TrainingActions<T>> completed
, потому что у вас уже есть информация о вашем животном в T
:
abstract class Trainer<T extends Animal> {
Set<TrainingActions<T>> completed = new HashSet<TrainingActions<T>>();
public void add(TrainingActions<T> t ) {
completed.add( t );
}
}
И у вас есть именно то, что вы хотите.
Использование:
DogTrainer tr = new DogTrainer();
tr.add( DogTrainer.Trainables.BITE );
Если вы уверены, что хотите использовать Enum для своего интерфейса:
public <E extends Enum<?> & TrainingActions<T>> void add( E t ) {
completed.add( t );
}
Ответ 3
Я отправлю это для дальнейшего рассмотрения, даже уже принятый ответ
Если вы прочитали этот вопрос, подумайте о том, чтобы использовать более свободные связанные элементы, с агрегацией и т.д., а не с высокой спецификой и сложностью иерархии типов и дженериков.
С помощью простой агрегации вы можете достичь, по крайней мере, одного и того же типа и логической безопасности, но с ослабленным соединением. Как я уже упоминал в комментариях:
Преимущественно лучше будет больше коллекций, агрегирования и т.д., чтобы обеспечить тип безопасности и эластичность вместе (разные тренеры с разными способности к разным животным в любых комбинациях, включая смешивание, не запирать человека, чтобы быть суперспецифическим мастером обучения для некоторых супер специфические способности конкретного животного и больше ничего).
Конечно, я имею в виду "Джон, который может научить лошадь рысью и собаку кору", а не "Джон, который может научить лошадь или собаку рысью или коре"
Ответ 4
Да, вы можете переместить ваши Trainables
, Set<Trainables> completed
и trainingComplete(Trainables t)
в класс Trainer.
Таким образом вам не нужно писать этот код каждый DogTrainer, HorseTrainer.. и так далее...
abstract class Trainer<T extends Animal>{
public enum Trainables implements TrainingActions<Animal>{
BARK, BITE, ROLLOVER, FETCH;
}
public Set<Trainables> completed = new HashSet<Trainables>();
public void trainingComplete(Trainables t){completed.add(t);}
}
В противном случае выполните abstractComplete (Trainables t) abstract, и вам нужно реализовать в каждом кластере внедрения Trainer.
abstract class Trainer<T extends Animal>{
public enum Trainables implements TrainingActions<Animal>{
BARK, BITE, ROLLOVER, FETCH;
}
public Set<Trainables> completed = new HashSet<Trainables>();
abstract public void trainingComplete(Trainables t);
}
Теперь ваш DogTrainer и HorseTrainer реализуют trainingComplete(Trainables t)
public class DogTrainer extends Trainer<Dog>{
public void trainingComplete(Trainables t){
completed.add(t);
}
}
public class HorseTrainer extends Trainer<Horse>{
public void trainingComplete(Trainables t){
completed.add(t);
}
}