Предложение шаблона проектирования для использования предметов из инвентаря

К сожалению, я делаю приключенческую игру с java и slick2d из-за моего школьного проекта по разработке объектно-ориентированного программного обеспечения.

Я применяю архитектуру MVC и создаю элементы, читая файл карты Tiled в классе ItemFactory. Кажется, они подходят для такой игры.

В игре есть несколько предметов, которые влияют на игру и особенно на персонажа. (персонаж такой же, как игрок в этом контексте)

Существует модель персонажа, и у нее есть внутренний класс Inventory, который в основном состоит из элементов ArrayList.

Контроллеры персонажей имеют функцию, называемую takeItem, которая принимает аргумент контейнера (т. е. полки), который также имеет список элементов ArrayList, а затем удаляет то, что внутри, и помещает в инвентарь.

И у него есть функция, которая называется useItem, принимает индекс int и использует элемент в соответствующем индексе.

До этого все нормально.

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

Чтобы поддерживать хороший дизайн, как и где я могу интерпретировать и придавать функции этим элементам?

Пока я думаю примерно так: 1- я не добавлял никакой функции в класс элемента. У него есть только имя, описание и Enumerated ItemType и int power 2. Я выполняю каждую работу в методе useItem() контроллера персонажа, т.е.

if (itemBeingUsed.getType() == Common.ItemType.POTION)
    `characterModel.increaseHealth(itemBeingUsed.getPower())`

Как видите, это кажется странным, потому что контроллер символов действует как интерпретатор элементов.

3- Другой подход может заключаться в создании класса ItemInterpreter, который принимает контроллер символов. Хотя это кажется более элегантным, это заставит меня выполнять кучу ненужных функций в контроллере персонажей, таких как функция «increaseCharacterHealth», которая вызывает функцию увеличенияHelth() модели персонажа и так далее.

Другая возможная проблема заключается в том, что я использую функции Контроллера персонажа для использования предметов, но предметы также могут влиять на игру, то есть замедлять время, но Контроллер персонажа находится ниже иерархии классов игры и не имеет доступа к функциям Game/GameController. Замедление времени игры и т. д. кажется не так, по крайней мере, сейчас, поэтому похоже, что я могу использовать предметы в классе CharacterController, но я хотел бы знать ваше мнение, хороший ли это дизайн или что может быть лучше.

Некоторые коды для устранения проблем: (примечание: они не завершены) Примечание. Я помещаю перечисляемые типы, такие как ItemType, Direction и т. д., в общий класс, который является общим для всех классов.

Используйте функцию элемента в CharacterController:

public void useItem(int index){ 
        Item item = ((Character)obj).inventory.takeItem(index);
        if (item != null) {
            if (item.getType() == Common.ItemType.POTION) {
                ((Character)obj).increaseHealth(item.getPower()); // I have extended a Person class, now I have to typecast to character everytime, which is also nonsense
            }
            else
                System.out.println("and other items..");
        }
        else
            System.out.println("Cannot use");
    }

Я не весь код выложил, но могу, если непонятно объяснил.


person TugRulz    schedule 16.11.2015    source источник
comment
Каждый элемент реализует элемент интерфейса, который имеет объявление «useItem». Все элементы должны реализовывать этот метод, и в контроллере вы знаете только то, что вызываете функцию useItem для экземпляра объекта. Это будет элемент, который имеет логику внутри своей реализации.   -  person lateralus    schedule 16.11.2015
comment
Это означает, что я собираюсь реализовать новый класс для каждого типа элементов. Не будет ли это излишним?   -  person TugRulz    schedule 16.11.2015
comment
Вы бы предпочли иметь переключатель для каждого типа элемента в вашем контроллере?   -  person lateralus    schedule 16.11.2015


Ответы (2)


Это классический случай, когда в игру вступают интерфейсы, наследование и полиморфизм. Всякий раз, когда вы используете switch или последовательность if, вы должны учитывать полиморфизм.

В этом случае определите интерфейс или абстрактный класс для используемого элемента.

public interface Useable {
   public void useItem(Person personUsing);
}

Теперь каждый элемент просто реализует пригодный для использования, и вы можете просто проверить, можно ли использовать элемент, если он отображает возможность его использования и вызывать метод при использовании.

person Tim B    schedule 16.11.2015
comment
Не будет ли слишком много отдельных классов предметов, если я сделаю это? - person TugRulz; 16.11.2015
comment
Вовсе нет, отдельные классы — это неплохо (если только это не сойдет с ума), и вы обнаружите, что это делает код намного чище, понятнее и эффективнее. - person Tim B; 16.11.2015

Как разработчик SOA (сервисно-ориентированной архитектуры) я бы не стал добавлять логику в свои элементы, потому что в такой ситуации становится очень сложно разложить код на множители и рационализировать его. Кроме того, помните, что композиция намного лучше наследования, если вам нужен масштабируемый код.

public interface ItemInterface { 
    public String getName();
    public String[] getCharacteristics(); 
}

Я бы определил некоторые эффекты предметов, обрабатывающие логику:

public interface ItemEffectInterface {
    public String getName();
    public void affectCharacter(ItemInterface item, CharacterInterface character, parameters);
    public void affectInventory(ItemInterface item, InventoryInterface inventory, parameters);
    public void affectGame(ItemInterface item, GameInterface game, parameters); 
}

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

Вы можете определить абстрактный класс AbstractItemEffect, если хотите определить поведение по умолчанию «ничего не делать» в эффектах вашего элемента-расширителя для всех методов и просто определить активные методы для каждого эффекта.

Затем, если вам нужна динамичная настраиваемая игра, вы можете определить взаимодействие предмета и эффектов в файле/базе данных. Вот пример JSON:

[
    {
        "item": "health potion",
        "effect": "heal",
        "parameters": {
            "hp": 100
        }
    },
    {
        "item": "mana potion",
        "effect": "regen",
        "parameters": {
            "mp": 150
        }
    },
    {
        "item": "mana potion",
        "effect": "drunk",
        "parameters": {
            "duration": 1000
        }
    },
    ...
[

Затем используйте класс обслуживания, чтобы сопоставить эффект с элементами при использовании:

public class ItemUser {
    private Map itemEffects = new HashMap();

    // Call it at service initialization after interpreting the configuration
    // (configuration is optional, you can call it directly of course).
    public void addItemEffect(ItemInterface item, ItemEffectInterface itemEffect, parameters) {
        Map effect = new HashMap();
        effect.push("effect", itemEffect);
        effect.pust("parameters", parameters);

        this.itemEffects.put(item.getName(), effect);
    }

    // Call it when you want to use an item.
    public void useItem(ItemInterface item, Character character, GameInterface game, InventoryInterface inventory) {
        Map itemEffects = this.itemEffect[item.getName()];

        // Process all the effects attached to your item 
        // on game, character, inventory, ...
    }
}

С помощью этой архитектуры вы можете прикрепить эффект ко многим элементам и элемент ко многим эффектам. Это должно предотвратить дублирование кода.

В то же время каждый предмет и эффект предмета будут иметь собственную сложность и не усложнят всю систему. Это не относится к шаблону «переключение регистра», подобному тому, который вы описываете в 2.. Представьте свой класс, если у вас есть 100 эффектов и 200 предметов...

Извините за мой плохой/неполный код JAVA!

person Gnucki    schedule 16.11.2015
comment
Здравствуйте, извините за поздний ответ, большое спасибо за ваш прекрасный ответ, я думаю, что смогу использовать его и для любых других элементов игры (оружие, доспехи и т. д.) - person TugRulz; 19.11.2015