Страницы

Поиск по вопросам

четверг, 23 января 2020 г.

Использование Strategy Pattern в случае, если конкретные стратегии зависят от типа использующего их объекта

#c_sharp #разработка_игр #проектирование #шаблон_стратегия


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

public interface iWeapon {
    void Use();
}

public interface iReloadable {
    void Reload();
}

public interface iWeaponUseStrategy {
    void Perform();
}

public interface iWeaponReloadStrategy {
    void Perform();
}

public class BaseWeapon: iWeapon, iReloadable {

    public iWeaponUseStrategy UseStrategy;
    public iWeaponReloadStrategy ReloadStrategy;

    public void Use() {
         UseStrategy.Perform();
    }

    public void Reload() {
        ReloadStrategy.Perform();
    }
}


Когда появляется класс оружия, стратегии для которого зависят от состояния самого
оружия начинаются трудности с адаптацией более общих стратегий для этого оружия. Текущая
реализация выглядит так:

public interface iWeaponWithAmmoUseStrategy {
    // Теперь стратегия меняет состояние внешнего по отношению к ней объекта
    void Perform(WeaponWithAmmo weapon); 
}

public class SimpleAmmoUseStrategy : iWeaponWithAmmoUseStrategy {

    public void Perform(WeaponWithAmmo weapon) {
        // Изменение состояния внешнего объекта
        weapon.AmmoCurrent -= 1;
        // код выстрела
    }
}

...

public class WeaponWithAmmo: iWeapon, iReloadable {
    public int AmmoMax {get; set;}
    public int AmmoCurrent {get; set;}

    public iWeaponWithAmmoUseStrategy UseStrategy;

    public void Use() {
        UseStrategy.Perform(this);
    }

    ...
}


Мне не нравится то, что невозможно использовать существующие стратегии для BaseWeapon
в новом WeaponWithAmmo. По логике стратегии iWeaponUseStrategy не имеют никаких специальных
требований для оружия, в котором они применимы. А стратегии iWeaponWithAmmoUseStrategy
"требуют" от оружия быть экземпляром класса WeaponWithAmmo или его подтипа. Тем не
менее, первые не могут быть использованы в оружии WeaponWithAmmo.

Единственное решение, которое приходит в голову, это вынести зависимость от оружия
в отдельный интерфейс, и для каждого нового класса оружия (и соответствующих ему стратегий)
менять интерфейс iWeaponUseStrategy:

// Теперь перед использованием оружие надо инициализировать его стратегии
public iWeaponWithAmmoInit {
    void Init(WeaponWithAmmo weapon);
}

// Но затем можно вызывать методы стратегий без ссылки на внешний объект
public iWeaponWithAmmoUseStrategy {
    void Perform();
}

// Кроме того, нужно чтобы базовая стратегия являлась подтипом специфичных для оружия
стратегий
// Меняем этот код каждый раз при добавлении класса оружия
public iWeaponUseStrategy : iWeaponWithAmmoUseStrategy, iSomeNewWeaponTypeUseStrategy {
    new void Perform();
}


Это очень некрасивое решение, нарушающее к тому же принцип "Существующий код должен
быть закрыт для изменений". 

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


Ответы

Ответ 1



Мне кажется, ошибка в том, что вы пытаетесь сложную систему правил закодировать в терминах иерархии объектов. Иерархии объектов просто не предназначены для этого. Хуже того, ваша логика правил использования оружия оказывается размазанной по всем иерархии наследования. Подобную проблему недавно обсуждал Эрик Липперт в цикле статей Wizards and warriors: [1], [2], [3], [4], [5]. (Вы можете просмотреть сразу выводы в 5-ой статье или прочитать всю промежуточную логику с начала цикла.) Присоединюсь к идее этих статей: просто создайте отдельную сущность «набор правил», в которой закодируйте все правила.

Комментариев нет:

Отправить комментарий