Вложенные перечисления с конструкторами и методами?

Я изучаю Java (относительно новый), работаю над клоном игры на Java в качестве побочного проекта. В игре игрок управляет персонажами. Каждый персонаж является частью фракции, и у каждой фракции есть определенный список навыков. Навыки одной фракции не могут быть использованы другой фракцией. Моя идея организовать это с помощью вложенных перечислений, где основное перечисление Skills имеет несколько внутренних перечислений (Faction1, Faction2 и т. д.). Идея состоит в том, что я смогу получить доступ к данным для любого конкретного навыка, используя что-то вроде Skills.Faction1.SKILL_NAME, и иметь доступ к полному списку навыков фракции, используя Skills.Faction1.values(). Упрощенный пример неудачной реализации этого выглядит следующим образом:


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

Когда я пытаюсь использовать эту реализацию, я получаю сообщения об ошибках, сообщающих мне, что конструкторы и/или поля для значений во внутренних перечислениях не существуют. Я попытался скопировать конструкторы и поля в каждое отдельное внутреннее перечисление, однако это было нечитаемо и не соответствовало потенциальным будущим навыкам, которые не будут привязаны к фракции. Этот упрощенный пример даже не включает все методы и альтернативные конструкторы, которые должны быть доступны для каждого навыка. Как я мог реализовать эту идею эффективно и элегантно таким образом, чтобы одновременно поддерживались навыки, которые являются членами фракции, и навыки, которые не являются, при условии, что я вообще мог бы реализовать ее? Я изо всех сил старался объяснить предполагаемые результаты кода, но если что-то все еще неясно, просто дайте мне знать. Спасибо.

Редактировать: было запрошено содержимое Faction1, поэтому в дополнение к тому, что я переписал свой первоначальный пример кода, чтобы, возможно, дать лучшее представление о моих намерениях, вот несколько разных способов, которые я пробовал Faction1. Все они были либо ошибочными, либо просто не идеальными.

Попытка 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;
    }
}

Моя первая попытка была именно такой, которая выдала мне ошибку The constructor Skills.Faction2(String) is undefined. Я понимаю, что это связано с тем, что Faction2 является собственным классом и не может использовать конструктор Skills, поэтому я перешел ко второй попытке.

Попытка 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;
    }
}

Это решение технически работает, ошибок нет. Однако моя проблема с этим решением заключается в безумном количестве дублирования кода, которое это вызывает в моей несокращенной программе. Каждый навык имеет множество полей, конструкторов и методов, независимо от того, назначен навык фракции или нет. Есть также множество фракций, которые необходимо создать. Если бы я когда-нибудь понял, что поле, метод или конструктор либо не нужны и должны быть удалены, либо необходимы и должны быть созданы, мне нужно будет создать или удалить их из каждой фракции отдельно. Честно говоря, это не то, чем я хочу заниматься в глупом побочном проекте.

Я не думал о каком-либо другом способе создания этих внутренних перечислений и не видел их в своих исследованиях, поэтому пока это мои единственные две реализации. Надеюсь, это немного прояснит ситуацию.

Можете ли вы показать полное содержимое перечисления Faction1? Потому что вы упустили вещи, которые определенно имеют отношение к делу. Сами константы можно опустить (оставим первую и последнюю), но все остальное здесь очень важно.

Rob Spoor 17.03.2022 19:35

Я благодарю вас за то, что вы занимаетесь своим обучением с помощью побочных проектов.

ernest_k 17.03.2022 19:40

Рассматривали ли вы возможность сделать faction атрибутом Skills, а затем просто сделать SNIPER_SHOT и JUDGEMENT значениями Skills? В зависимости от того, как вы пишете свой код, вы должны иметь возможность перечислять навыки по фракциям, если это было причиной, по которой вы сделали их отдельными перечислениями.

ernest_k 17.03.2022 19:42

@RobSpoor Я обновил свой вопрос, указав содержание Faction1, а также в целом улучшил пример кода, чтобы попытаться немного прояснить ситуацию.

Max 17.03.2022 21:15

Подумайте о том, чтобы сделать дробь enum и использовать ее так же, как любой другой параметр.

Johannes Kuhn 06.04.2022 08:24
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
5
73
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Краткий урок ООП для начала - Сущности Faction и Skill, похоже, имеют отношения has a, то есть A Factions HAS A set of Skills. Также я создам дополнительное перечисление SkillName, которое снова имеет отношение HAS A к Skill. Итак, помня об этом, вы можете организовать свои перечисления так:

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

Теперь вы можете получить доступ к навыкам и конкретному навыку, например, --

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

Шаблоны доступа не совсем такие, как вы хотели, но они работают примерно в той же степени.

ПРИМЕЧАНИЕ -

Не знаю, какой тип игры вы разрабатываете, но в большинстве игр навыки игроков улучшаются (т. е. изменяются свойства навыков), а также игроки приобретают новые навыки. Следовательно, приведенная выше настройка станет очень жесткой, поскольку перечисления неизменяемы, и вы не сможете масштабировать это. Вместо этого я бы предложил создать классы для фракций и навыков вместо перечислений. Таким образом, вы сможете их мутировать.

Спасибо за ваше предложение, однако я не упомянул в посте, что я пришел к этой идее именно по той причине, что у избегать есть карты. Моя система до этой новой идеи синтаксиса была чем-то вроде Map<String, Skills[]> factionSkills = new HashMap<String, Skills>() {{put("FACTION_NAME", new Skills[] {SKILL1...});}}. Однако, если я не смогу придумать лучшее решение, я обязательно запомню ваше! Благодарю вас!

Max 17.03.2022 20:46
Ответ принят как подходящий

Просто решил перейти на Java 9, чтобы использовать Map.of(). Спасибо за вашу помощь!

Ваш ответ может быть улучшен с помощью дополнительной вспомогательной информации. Пожалуйста, редактировать добавьте дополнительную информацию, например цитаты или документацию, чтобы другие могли подтвердить правильность вашего ответа. Дополнительную информацию о том, как писать хорошие ответы, можно найти в справочном центре.

Community 26.03.2022 21:33

Похоже, вы уже нашли свое решение, но вот что-то, что, вероятно, намного проще в использовании.

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

}

Другие вопросы по теме