“Sets” of a particular enum type but with generics

后端 未结 4 1979
北荒
北荒 2021-01-31 16:24

Let\'s say I have an abstract class

public abstract class Trainer{}

I have specific trainers like :

pub         


        
相关标签:
4条回答
  • 2021-01-31 16:35

    Despite design analysis, I think the most your-situation (closest to your already created design) answer is:

    • your DogTrainer.Trainables is TrainingActions<Dog>
    • your HorseTrainer.Trainables is TrainingActions<Horse>

    Generally, your Trainables is already a TrainingActions<T extends Animal>

    so you can just provide Set<TrainingActions<T>> completed, because you already have your animal information in T:

    abstract class Trainer<T extends Animal> {
        Set<TrainingActions<T>> completed = new HashSet<TrainingActions<T>>();
    
        public void add(TrainingActions<T> t ) {
            completed.add( t );
        }
    }
    

    And you have exactly what you want.

    Usage:

    DogTrainer tr = new DogTrainer();
    tr.add( DogTrainer.Trainables.BITE );
    

    If you are sure you want to enforce an Enum for your interface:

    public <E extends Enum<?> & TrainingActions<T>> void add( E t ) {
       completed.add( t );
    }
    
    0 讨论(0)
  • 2021-01-31 16:40

    I will post that for further consideration, even there is already accepted answer

    If you read this question, consider using more loose coupled elements, with aggregation etc. instead of highly specific and complicated type hierarchy and generics.

    With just plain aggregation you can achieve at least the same type & logical safety, but loose coupled design. Like I mentioned in comments:

    Propably better will be more collections, agregations etc. to ensure type safety and elasticyty together (different trainers with different abilities to different animals in any combinations, including mixing, not locking a person to be a super-specific training master for some super specific abilities of specific animal and nothing more).

    Of course I mean a "John than can teach horse to trot and a dog to bark", not "John that can teach horse or dog to trot or bark"

    0 讨论(0)
  • 2021-01-31 16:46

    Yes you can move your Trainables , Set<Trainables> completed and trainingComplete(Trainables t) to the class Trainer.

    This way you no need to write these code each DogTrainer, HorseTrainer .. and so on...

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

    Otherwise make trainingComplete(Trainables t) abstract and you need to implement in each Trainer implementaion clasees.

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

    Now your DogTrainer and HorseTrainer are implementing 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);
         }
     }
    
    0 讨论(0)
  • 2021-01-31 16:53

    To specify a bound to an interface and that it be an enum, you need a generic intersection, which looks like:

    class MyClass<T extends Enum<T> & SomeInterface> {}
    

    Note that when intersecting a class and an interface(s), the class must appear before the interface(s).

    In this case, the generics Kung Fu you want is one level more complicated, because the interface of the TrainingActions enum must itself refer back to the animal type.

    class Trainer<A extends Animal, T extends Enum<T> & TrainingActions<A>> {}
    

    A complete working example, based on your posted code, that compiles is:

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

    But I would go one step further and define several TrainingActions enums in the class of the particular Animal to which they apply:

    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> {}
    
    0 讨论(0)
提交回复
热议问题