Это правильный способ построения иерархии классов?

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

У меня есть базовый интерфейс для сущностей (которые являются либо игроками, либо монстрами):

public interface Entity {
public int getHP();
...
// all getters that denote all stats the Entities have in my game, nothing
else is in the interface
}

Тогда есть второе расширение интерфейса Entity, называется Classes, который содержит все сеттеры, относящиеся к классам:

public interface Classes extends  Entity {
void setHP(int a);
... //and so on}

Наконец-то добраться до какого-то реального класса, класса PlayerClasses отвечает за построение классов:

public class PlayerClasses implements Classes {    
private int HP, ATK, DEF, DAMAGE, DROP;

@Override
public int getHP() {
    return HP;
}

@Override
public void setHP(int a) {
    HP = a;
}

// this is how I build the player class
public void Rogue(){
   setHP(150);
   setATK(30);
   setDEF(20);
   setDAMAGE();
}

И, наконец, класс, конструктор которого используется для создания экземпляра проигрывателя, который затем передается в другие модули (нет наследования или прямого доступа к любому другому полю класса или не требуется какого-либо состояния в конструкторе, так что победите, верно?)

public class Player {
private int HP,ATK,DEF,DAMAGE,DROP;
private PlayerClasses c;

public Player() {
    c = new PlayerClasses();
    c.Rogue();
    HP = c.getHP();
    ATK = c.getATK();
    DEF = c.getDEF();
    DAMAGE = c.getDAMAGE();
    DROP = c.getDrop();
}

Вроде длинный вопрос, но я старался сохранить его вежливым. Любая обратная связь очень ценится.

Редактировать: Хорошо, чтобы прояснить, почему я выбираю такой дизайн, я хочу, чтобы экземпляр проигрывателя был неизменным объектом, который можно создавать только с правильными значениями, сохраняя при этом инициализацию в других модулях настолько чистой, насколько это возможно, без каких-либо зависимостей. Так, например, значения из двух разных классов игроков не могут смешиваться в случае, если это экземпляры в модуле, которые показывают статистику игрока и статистику монстров. Я чувствую, что передача частных переменных HP и ATK по наследству, а затем перетаскивание пространства имен с одинаковыми переменными, к примеру, не способ.

1 ответ

Решение

Я думаю, что я не понимаю причину неизменности Player класс, который содержит PlayerClass, Но в любом случае, IMO ваш Player класс - это то, что должно наследовать Entity черта характера. Не PlayerClasses объект, который используется в качестве шаблона (?). Потому что какой смысл иметь Player и я предполагаю, что аналогично построенный Monster класс, если они не оба Entity?

Вы также смешиваете ответственность / абстракции странным образом. Что это такое, что заключено в PlayerClasses и в Player? PlayerClasses выглядит так, как будто он должен представлять тип класса типа "Rogue", а не фактический игрок. И для этого у него не должно быть методов установки, и ни один из них не является типом класса. И фабричный метод, который инициализирует объект PlayerClasses, имеет "плохой" стиль. Вы должны всегда пытаться гарантировать, основываясь только на типе класса, что все правильно, не иметь магических методов, которые нужно вызывать для правильности объектов (то есть, нет методов init, кроме конструктора).

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

Вот черновик того, как я бы это сделал:

interface PlayerClass {
    int getBaseHp();
    int getBaseAtk();
    ... // more class attributes
}

// as utility so I don't have to write 20 full classes
abstract class PlayerClassBase implements PlayerClass {
    private final int hp, atk, ..;
    protected PlayerClassBase(int hp, int atk, ...) {
        this.hp = hp;
        this.atk = atk;
    }
    @Override
    public int getBaseHp() {
        return hp;
    }
    ....
}

// short class but guaranteed that every instance of Rogue has the correct values
class Rogue {
     public Rogue() {
         super(40, 23, 123, ...);
     }
}

// something to represent entities
interface Entity {
    int getCurrentHp();
    int takeDamageFrom(Entity other);
    ...
}

// maybe an abstract base class here as well

// represents a player that has an immutable class and it can't exist without
class Player implements Entity {
    privte final PlayerClass playerClass;
    private int currentHp;
    ...

    public Player(PlayerClass playerClass) {
        this.playerClass = playerClass;
        currentHp = playerClass.getHp();
        ...
    }
    public int takeDamageFrom(Entity other) {
        currentHp -= other.getCurrentAtk();
        return currentHp;
    }

}

Часть PlayerClass также может быть простой enum вместо большой иерархии классов.

enum PlayerClass {
    ROGUE(23, 23, 4, ...),
    ...
    ;
    private final int hp;
    PlayerClass(int hp, int atk, ...) {
        this.hp = hp;
        ...
    }
    public int getHp() { return hp; }
    ...
}

Таким образом, вы могли бы статически ссылаться PlayerClass.ROGUE и создайте игрока как это: new Player(PlayerClass.ROGUE), Вместо в настоящее время new Player(new PlayerClass().Rogue()), Или с большой иерархией: new Player(new Rogue())

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