Nested Enums with Constructors & Methods?

Viewed 109

I'm a Java student (relatively new) working on a game clone in Java as a side project. In the game, the player controls characters. Each character is part of a faction, and each faction has a set list of skills. The skills of one faction can not be used by any other faction. My idea for how to organize this is with nested enums, where a main Skills enum has multiple inner enums (Faction1, Faction2, etc). The idea is that I would be able to access the data for any specific skill using something along the lines of Skills.Faction1.SKILL_NAME, and to be able to access a full list of a faction's skills using Skills.Faction1.values(). A simplified example of a failed implementation of this is as follows:


public enum Skills {
    FACTIONLESS_SKILL("arbitraryArgs"); //More skills than just this one
    
    enum Faction1 {
        FACTION1_FIRST_SKILL("arbitraryArgs"), //More skills between the 2
        FACTION1_LAST_SKILL("arbitraryArgs");
        ...
    }
    
    enum Faction2 {
        FACTION2_FIRST_SKILL("arbitraryArgs"), //More skills between the 2
        FACTION2_LAST_SKILL("arbitraryArgs");
        ...
    }
    
    String arbitraryField; //1 of many fields universal among both factionless and factioned skills
    
    Skills(String arbitraryArgs) { //1 of many universal constructors
        this.arbitraryField = arbitraryArgs;
    }

    void doUniversalThing() {
        //Code here
    }
}

When I try to use this implementation, I get errors telling me that the constructors and/or fields for the values in the inner enums don't exist. I tried copy pasting the constructors and fields into each individual inner enum, however that was both unreadable and unaccommodating to potential future skills which would not be attached to a faction. This simplified example doesn't even include all of the methods and alternate constructors that must be accessible to each skill. How could I implement this idea effectively and elegantly in a way that both supports skills which are a member of a faction and skills which are not, assuming that I could implement it at all? I tried my best to explain the intended results of the code, but if anything is still unclear then just let me know. Thank you.

Edit: The contents of Faction1 were requested, so in additon to me rewriting my initial code example to maybe give a better idea of my intentions, here's a few different ways I've tried Faction1. All were either erroneous or just not ideal.

Attempt 1:

public enum Skills {
    FACTIONLESS_SKILL("arbitraryArgs"); //More skills than just this one
    
    enum Faction1 {
        FACTION1_FIRST_SKILL("arbitraryArgs"), //More skills between the 2
        FACTION1_LAST_SKILL("arbitraryArgs");
    }
    
    String arbitraryField; //1 of many fields universal among both factionless and factioned skills
    
    Skills(String arbitraryArgs) { //1 of many universal constructors
        this.arbitraryField = arbitraryArgs;
    }
}

My first attempt was just this, which gave me an error that The constructor Skills.Faction2(String) is undefined. I understand that this is due to Faction2 being its own class and unable to use a Skills constructor, which is why I then moved to my second attempt.

Attempt 2:

public enum Skills {
    FACTIONLESS_SKILL("arbitraryArgs"); //More skills than just this one
    
    enum Faction1 {
        FACTION1_FIRST_SKILL("arbitraryArgs"), //More skills between the 2
        FACTION1_LAST_SKILL("arbitraryArgs");
        
        String arbitraryField; Duplicates of Skills fields
        
        Faction1(String arbitraryArgs) { //Duplicate of Skills constructor
            this.arbitraryField = arbitraryArgs;
        }
    }
    
    String arbitraryField; //1 of many fields universal among both factionless and factioned skills
    
    Skills(String arbitraryArgs) { //1 of many universal constructors
        this.arbitraryField = arbitraryArgs;
    }
}

This solution technically works, in that there are no errors. However, my issue with this solution is the insane amount of code duplication this causes in my non-reduced program. Every skill has numerous fields, constructors, and methods, whether the skill is assigned to a faction or not. There are also numerous factions that would need to be made. If I ever realized that a field or method or constructor was either unneeded and should be removed or needed and should be created, I would need to create or remove it from every faction individually. This is just honestly not something I want to do on a silly side project.

I haven't thought of any other way to create these inner enums, nor have I seen any in my research, so these are my only 2 implementations so far. I hope this clears things up a bit.

3 Answers

A brief OOPs class to begin with -- The Faction and Skill entities seem to have a has a relationship i.e., A Factions HAS A set of Skills. Also I will create an additional enum SkillName which again has a HAS A relation ship with Skill. So keeping these in mind you could arrange you enums like so --

public enum Faction {
    FACTION1(new HashMap<SkillName, Skill>(){{
        put(Skill.SNIPER_SHOT.skillName(), Skill.SNIPER_SHOT);
    }}),
    FACTION2(new HashMap<SkillName, Skill>(){{
        put(Skill.JUDGEMENT.skillName(), Skill.JUDGEMENT);
    }});

     Map<SkillName, Skill> skills;
    Faction(Map<SkillName, Skill> skills) {
        this.skills = skills;
    }

    public Skill[] skills(){
        return this.skills.values().toArray(new Skill[0]);
    }

    public Skill skill(SkillName name){
        Skill skill =  this.skills.get(name);
        if(Objects.isNull(skill)){
            throw new IllegalArgumentException("Invalid Skill name");
        }
        return skill;
    }

}

enum SkillName {
    SNIPER_SHOT("SNIPER_SHOT"),
    JUDGEMENT("JUDGEMENT");
    String value;

    SkillName(String value){
        this.value = value;
    }

}

enum Skill {
    SNIPER_SHOT(SkillName.SNIPER_SHOT, 0, 95, 5, new boolean[] {true, true, false, false}, new boolean[] {false, true, true, true}),
    JUDGEMENT(SkillName.JUDGEMENT,-25, 85, 5, new boolean[] {true, true, false, false}, new boolean[] {true, true, true, true});
    SkillName name;
    Integer dmg;
    Integer acc;
    Integer crit;
    boolean[] rank;
    boolean[] target;
    Skill(SkillName name, Integer dmg, Integer acc, Integer crit, boolean[] rank, boolean[] target) {
        this.name = name;
        this.dmg = dmg;
        this.acc = acc;
        this.crit = crit;
        this.rank = rank;
        this.target = target;
    }

    public SkillName skillName() {
        return this.name;
    }
}

Now you could access the skills and a specific skill like so, --

Faction.FACTION1.skills();
Faction.FACTION2.skills();
Faction.FACTION1.skill(SkillName.SNIPER_SHOT);
Faction.FACTION1.skill(SkillName.JUDGEMENT); // this should throw exception
Faction.FACTION2.skill(SkillName.SNIPER_SHOT); // this should throw exception
Faction.FACTION2.skill(SkillName.JUDGEMENT);

The access patterns are not exactly the same as you wanted, but these work pretty much to the same extent.

SIDE NOTE -

Not sure what type of game you are developing, but in most of the games, skill of players improve (i.e. skill properties change) and players acquire new skills as well. Hence the above setup will become very rigid as enums are immutable and you will not be able to scale this. Instead, what I would suggest is that you create classes for Factions and Skills instead of enums. This way you would be able to mutate them.

Just decided to upgrade to Java 9 to use Map.of(). Thank you all for your help!

Seems like you already found your solution, but here is something that is probably much easier to use.

import java.util.Arrays;
import java.util.List;

/** Enums. https://stackoverflow.com/questions/71517372 */
public class SOQ_20220406_0201
{

   /**
    * 
    * Main method.
    * 
    * @param   args  commandline arguments, should they be needed.
    * 
    */
   public static void main(String[] args)
   {
   
      enum Faction
      {
      
         F1,
         F2,
         F3,
         ALL,
         ;
      
      }
      
      enum Skill
      {
      
         SKILL_1(Faction.F1, "arbitraryArgs"),
         SKILL_2(Faction.F2, "arbitraryArgs"),
         SKILL_3(Faction.F3, "arbitraryArgs"),
         SKILL_ALL_CAN_USE(Faction.ALL, "arbitraryArgs"),
         ;
         
         private final Faction faction;
         private final String arbitraryArgs;
         
         Skill(Faction faction, String arbitraryArgs)
         {
         
            this.faction = faction;
            this.arbitraryArgs = arbitraryArgs;
         
         }
         
         public static List<Skill> fetchAllSkillsForFaction(final Faction faction)
         {
         
            return
               Arrays.stream(values())
                  .parallel()
                  .filter(each -> each.faction == faction)
                  .toList()
                  ;
         
         }
      
      }
      
      System.out.println(Skill.fetchAllSkillsForFaction(Faction.F1));
   
   }

}
Related