среда, 26 сентября 2012 г.

Фабричный метод (Фабрика)

Курим паттерны вместе. Для начала что-нибудь несложное. К примеру, Фабричный метод или Фабрика (Factory).

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

Для начала создадим базовый класс Unit. Это будет абстрактный класс, в котором будет содержаться общий для всех метод Attack();



public abstract class Unit
    {
        public abstract String Attack();
    }

Теперь занаследуем от этого базового класса ещё три класса: Лучник, Маг и Пехотинец.

  
    public class ElfArcher : Unit
    {
        public override String Attack()
        {
            return "Эльфийский лучник отстрелялся!";
        }
        
    }

    public class ElfMage : Unit
    {
        public override String Attack()
        {
            return "Эльфийский маг колданул!";
        }


    }

    public class ElfGrunt : Unit
    {
        public override String Attack()
        {
            return "Эльфийский пехотинец стукнул!";
        }
    }

У всех этих методов абстрактный метод надкласса перекрыт соответствующим кодом.

Ну, и код, который это всё вызывает (обычная консоль).

    class Program
    {
        static void Main(string[] args)
        {
            List<unit> units = new List<unit>();

            units.Add(new ElfArcher());
            units.Add(new ElfGrunt());
            units.Add(new ElfMage());

            foreach (Unit u in units)
            {
                Console.WriteLine(u.Attack());
            }

            Console.ReadLine();
        }
    }

Смысл кода понятен - создаём список юнитов, а затем по очереди заставляем каждого поатаковать. Всё понятно, всё хорошо.

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

Кроме того, если у нас в коде используется конструктор, то перед каждым его использованием нам необходимо обязательно прописывать это условие проверки. А если этих конструкторов 100500?!!

Клиент не должен знать ограничения расы на количество юнитов. Поэтому, генерацию юнитов эльфов выносим в отдельный модуль, который и будет знать, достаточно ли эльфов на карте или надо ещё. Этот модуль реализует паттерн "Фабрика".

        /// <summary>
    /// Эльфийская фабрика
    /// </summary>
    public class ElvenFactory
    {
        protected Byte _elvesCount = 0;

        /// <summary>
        /// Создание эльфийского лучника
        /// </summary>
        /// <returns>Эльфийский лучник</returns>
        public ElfArcher CreateArcher()
        {
            if (_elvesCount < 10)
            {
                _elvesCount++;
                return new ElfArcher();
            }
            else
            {
                throw (new Exception("Количество эльфов превышено!"));
            }
        }

        /// <summary>
        /// Создание эльфийского мага
        /// </summary>
        /// <returns>Эльфийский маг</returns>
        public ElfMage CreateMage()
        {
            if (_elvesCount < 10)
            {
                _elvesCount++;
                return new ElfMage();
            }
            else
            {
                throw (new Exception("Количество эльфов превышено!"));
            }
        }

        /// <summary>
        /// Создание эльфийского пехотинца
        /// </summary>
        /// <returns>Эльфийский пехотинец</returns>
        public ElfGrunt CreateGrunt()
        {
            if (_elvesCount < 10)
            {
                _elvesCount++;
                return new ElfGrunt();
            }
            else
            {
                throw (new Exception("Количество эльфов превышено!"));
            }
        }
    }

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

    class Program
    {
        static void Main(string[] args)
        {
            List&ltUnit&gt units = new List&ltUnit&gt();
            
            //Добавилась строчка - создание фабрики
            ElvenFactory factory = new ElvenFactory();

            units.Add(factory.CreateArcher());
            units.Add(factory.CreateGrunt());
            units.Add(factory.CreateMage());

            foreach (Unit u in units)
            {
                Console.WriteLine(u.Attack());
            }

            Console.ReadLine();
        }
    }

Что мы в итоге получили? Теперь, после замены простого создания "в лоб" на создание объектов через фабрику, мы получили единый централизованный способ создания объектов сразу с проверкой условий их создания.

Если подходить совсем строго, то у нас тут разместилось аж три фабричных метода: для магов, лучников и пехотинцев. Чего нам не хватает? Мы можем замечательно создавать полчища прекрасных эльфов.

Однако, если нам понадобится создать орду безобразных орков, то данный подход не сработает. И тогда нам понадобится создавать уже отдельную, другую фабрику с блекджеком и орчихами другим количеством возможных юнитов или ещё какими-либо параметрами.

Резюме
Когда мы создаём объекты в коде просто так - можно использовать слово "new". Однако, если создание объектов нам по какой-то причине надо абстрагировать от остального кода, то в качестве средства создания объектов можно (и должно) использовать паттерн "Фабрика".

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

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