Factory pattern. Пример применения в Unity

  • Tutorial

Фабричных методов на самом деле много.
Даже базовые конструкции if else являются фабричным методом, так как и switch.
Но ведь это не удобно читать и расширять.


Представим простой пример. У нас есть игра slasher с монстрами. У нас есть несколько режимов: easy, medium, hard. Следовательно пусть у нас будут отличаться у монстров жизни и урон. Как организовать удобное создание монстров и в одном месте? Все в подcut`e.


1. Создадим примерное описание монстра в виде Description.


[Serializable]
public class MobDescription
{
    public float MaxHealth;
    public float Damage;
} 

2. Заполним наших монстров по уровням


Способов заполнить такие Descriptions на самом деле много, будь то json, xml или же просто ScriptableObject. Для примера воспользуемся ScriptableObject и создадим класс списков описаний монстров.


[CreateAssetMenu(fileName = "MobDescriptions", menuName = "MobDescriptions", order = 51)]
public class MobDescriptions : ScriptableObject
{
    [SerializeField] private List<MobDescription> _listOgre;
    [SerializeField] private List<MobDescription> _listTroll;

    public List<MobDescription> ListOgre => _listOgre;
    public List<MobDescription> ListTroll => _listTroll;
}

И получим вот это.


Так как мы делаем 3 уровня сложности, то давайте заполним


Хорошо у нас теперь есть все описания по уровням сложности для наших монстров.


3. Создадим Model для mvc монстра.


Это лишь шаблон использования расширения модели, для того чтобы не хранить данные о монстре прямо в модели.


public class MobModel
{
    private MobDescription _description;
    private float _currentHealth;

    public MobDescription Description => _description;

    public MobModel(MobDescription description){
        _description = description;
        _currentHealth = _description.MaxHealth;
    }

    // Ваша логика монстра
}

4. Теперь создадим класс фабрики, которая будет создавать монстров в зависимости от уровня сложности. Такую фабрику легко расширить, а так же добавлять другие фабрики для генерацию дропа или спавна объектов.


public class Factory
{
    private Dictionary<string, Func<int, MobModel>> mobFactory;

    public void Init(MobDescriptions descriptions)
    {
        mobFactory = new Dictionary<string, Func<int, MobModel>>()
        {
            {"ogre", (level) => new MobModel(descriptions.ListOgre[level])},
            {"troll", (level) => new MobModel(descriptions.ListTroll[level])}
        };

    }

    public MobModel CreateMobModel(string nameMob, int level)
    {
        return mobFactory[nameMob](level);
    }
}

5. Ну и конечно же пример вызова создания монстра.


Пока нет никакой логики используем хардкод. В итоговом варианты это может быть доп описание уровня, в котором будут параметры для спавна монстров с нормальными условиями. Например когда игрок прошел конкретную точку, спавнятся N enemy X уровня.


public class GameManager : MonoBehaviour
{
    [SerializeField] private MobDescriptions _mobDescriptions;

    private Factory _factory;

    private void Start()
    {
        _factory = new Factory();
        _factory.Init(_mobDescriptions);

        _factory.CreateMobModel("ogre", 2);
    }
}

Комментарии 7

    +1
    Даже базовые конструкции if else являются фабричным методом, так как и switch.

    Вы не могли бы развернуть это утверждение?

      0
      if (nameMob == "ogre")
      {
          return new MobModel(descriptions.ListOgre[level]);
      }
      
      if (nameMob == "troll")
      {
          return new MobModel(descriptions.ListTroll[level]);
      }

      тоже самое на swich


      switch (nameMob)
              {
                  case "ogre":
                      return new MobModel(descriptions.ListOgre[level]);
      
                  case "troll":
                      return new MobModel(descriptions.ListTroll[level]);
              }

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

        0

        Ну давайте, что ли, на примерах.


        Является ли вот такой код фабричным методом?


        public bool IsBig()
        {
          if (Length > 5)
            return true;
          else
            return false;
        }

        А вот такой?


        public void ReadABook()
        {
          Book book;
          if (Hour > 17)
            book = new EveningBook();
          else
            book = new DayBook();
          book.Read()
        }
          +1
          Фабрика, в любых ее проявлениях, позволяет отложить указание типа создаваемого объекта до времени исполнения. Наличие или отсутствие логического ветвления в методе ни о чем не говорит.
            0
            указание типа

            Наверное, все же, указание реализации.
            Ну и про время исполнения тоже сложно — наверняка на шаблонах тоже можно запилить фабрику.

              0

              Я несколько раз начинал и стирал свой ответ пока не понял почему слово "реализация" в этом контексте мне не нравится.


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


              Есть и другие способы выбора реализации (даже если относится к термину очень вольно), например использовать делегаты вместо методов или даже просто через ветвление.


              Поэтому мне кажется что слово "тип" тут больше подходит.

        0

        .

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

        Самое читаемое