Подтвердить что ты не робот

"Устанавливает" определенного типа перечисления, но с дженериками

Скажем, у меня есть абстрактный класс

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.

Возможно ли это?

4b9b3361

Ответ 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);
     }
 }