Factory Method Pattern

    Привет, друзья. С вами Alex Versus

    Ранее мы говорили про шаблоны проектирования Одиночка и Стратегия, про тонкости реализации на языке Golang.

    Сегодня расскажу про Фабричный метод. 

    В чем суть?

    Фабричный метод (Factory method) также известный как Виртуальный конструктор (Virtual Constructor) - пораждающий шаблон проектирования, определяющий общий интерфейс создания объектов в родительском классе и позволяющий изменять создаваемые объекты в дочерних классах. 

    Шаблон позволяет классу делегировать создание объектов подклассам. Используется, когда:

    1. Классу заранее неизвестно, объекты каких подклассов ему нужно создать.

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

    3. Создаваемые объекты родительского класса специализируются подклассами.

    Какую задачу решает?

    Представьте, что вы создали программу управления доставкой еды. В программе в качестве единственного средства доставки используется электро-самокат. Ваши курьеры на электро-самокатах развозят еду из пункта А в пункт Б. Все просто. 

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

    Вы обнаруживаете, что большая часть ваших сущностей в программе сильно связаны с объектом Самокат и чтобы заставить вашу программу работать с другими способами доставки, вам придется добавить связи в 80% вашей кодовой базы и так повторить для каждого нового транспорта. Знакомая ситуация? 

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

    И какое решение?

    Фабричный метод предлагает создавать объекты транспорта через вызов специального метода. Подклассы класса, который содержит фабричный метод могут изменять создаваемые объекты конкретных создаваемых транспортов. На первый взгляд, это может показаться бессмысленным: мы просто переместили вызов конструктора из одного конца программы в другой. Но теперь вы сможете переопределять фабричный метод в подклассе, чтобы изменить тип создаваемого транспорта.

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

    Для клиента фабричного метода нет разницы между создаваемыми объектами, так как он трактует их как некий абстрактный Транспорт. Для него важно, чтобы данный объект мог доставить еду из пункта А в пункта В, а как конкретно он это будет делать, неважно. 

    Посмотрим на диаграмму классов такого подхода.

    Диаграмма классов Factory Method
    Диаграмма классов Factory Method

    Реализация на Golang

    Пример реализации на PHP, можно изучить тут. Так как в Golang отсутствуют возможности ООП, такие как классы и наследование, то реализовать в классическом виде этот шаблон невозможно. Несмотря на это, мы можем реализовать базовую версию шаблона - Простая фабрика.

    В нашем примере есть файл iTransport.go,  который определяет методы создаваемых транспортных средств для доставки еды. Сущность транспорта будем хранить в структуре (struct), которая применяет интерфейс iTransport.

    Также реализуем файл Factory.go, который представляет фабрику создания нужных объектов. Клиентский код реализован в файле main.go. Вместо прямого создания конкретных объектов транспорта клиентский код будет использовать для этого метод фабрики getTransport(t string), передавая нужный тип объекта в виде аргумента функции. 

    Когда применять?

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

    2. Фабричный метод отделяет код создания объектов от остального кода. Код создания объектов можно расширять, не трогая основной код программы. Для создания нового объекта вашего продукта достаточно создать новый подкласс и определить в нем фабричный метод, возвращающий нужный продукт в нужной конфигурации.

    Какие преимущества?

    1. Избавляет слой создания объектов от конкретных классов продуктов. Выделяет код производства продуктов в одно место, упрощая поддержку кода.

    2. Упрощает добавление новых продуктов в программу.

    3. Реализует принцип открытости/закрытости (англ. open–closed principle, OCP) — принцип ООП, устанавливающий следующее положение: «программные сущности (классы, модули, функции и т. п.) должны быть открыты для расширения, но закрыты для изменения»

    Какие недостатки?

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

    Итог

    Используйте шаблон Фабричный метод в случае, когда вы хотите без проблем внедрять в вашу программу новые объекты с новыми конфигурациям для взаимодействия с основной бизнес-логикой. 

    Рад был поделиться материалом, Alex Versus. Публикация на английском.
    Всем удачи!

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

    Используете ли вы шаблоны проектирования при разработке на Golang?

    • 26,1%Да6
    • 8,7%Нет2
    • 65,2%Шаблоны на Golang это зло15

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

      –4
      Спасибо за шикарное представление данного паттерна на языке golang. Молодец Алекс, так держать!!!
        0
        Спасибо за обратную связь.
        +2
        Когда применять?

        Когда пишем не на Go. А на Go так писать не надо, плз.

          +1

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

            +1
            Ну, не совсем. Тут имеет место абстрактная функция, которая может переопределяться в классах-наследниках. Т.е. вся суть в том, что ее поведение еще и может расширяться впоследствии, и не трогая оригинал. Т.е. не одна функция, а одна базовая и несколько расширений.

            При этом у автора:

            Так как в Golang отсутствуют возможности ООП, такие как классы и наследование


            Для создания нового объекта вашего продукта, достаточно создать новый подкласс


            Обычно в ООП когда говорят «подкласс», подразумевают наследование. При этом автор вот эту вот вторую часть про расширение скромно опустил как очевидную (и как явно написано, реализовал-то он другую версию шаблона, а не Factory Method), и вот этого противоречия в своем тексте видимо не наблюдает.
              0

              Ну да, примеры об одном, текст о другом.

              +1

              В том, что написали Вы — ничего плохого нет. В том, что написано в статье — плохо примерно всё. Что конкретно плохо — по большей части Вы же сами и описали комментарием ниже.

            0
            Ваш код ужасен. Изучите Go пожалуйста.
              +2
              Спасибо за обратную связь. Примеры специально делал без строгого соответствия код-стайлу. Но хотелось бы получить более развернутую обратную связь от Вас, в чем конкретно «ужасность» кода?
                +3

                Интерфейс называется iTransport - так не принято, скорее Transport или Transportable.

                У геттеров предпочтительнее не использовать префикс get: name() вместо getName()

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

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

                Функция создания структуры возвращает интерфейс, хотя идиоматичнее возвратить конкретный тип: в Go т.н. structural typing поэтому паттерны из Java/PHP не так работают: предпочтительнее возвращать конкретные типы, а определять и использовать интерфейсы уже на уровне консьюмера/клиента, тогда структура автоматически приведется к интерфейсу без строгой номинальной привязки. Т.е. некий код говорит, что он хочет принимать в качестве аргумента нечто, что ведет себя как утка, и приведение конкретного типа к интерфейсу происходит в этот момент. Неидиоматично объявлять, что некая структура изначально удовлетворяет какой-то интерфейс (что это её якобы неотъемлемое свойство).

                  0
                  Спасибо за конструктивный ответ. С вами согласен по всем пунктам.

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

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