Шаблон Abstract Factory (Абстрактная фабрика) на PHP
02-07-2019Reading time ~ 2 min.PHP/ООП 10798
Паттерн «Абстрактная фабрика» довольно распространён, особенно часто приходится слышать выражение «использовать фабрику» среди программистов, которые делают вид, что разбираются в шаблонах проектирования. :)
Строго говоря, «фабрик» две: абстрактная фабрика и фабричный метод. Основная суть у них похожа, но разнится реализация. Поэтому желательно уточнять какая именно фабрика используется. Сейчас поговорим про Abstract Factory.
Исходный демо-пример вы найдета на гитхабе, посмотрите его, прежде читать дальше.
Главный смысл абстрактной фабрики в том, чтобы иметь несколько однотипных объектов с гарантированным методом (интерфейсом), но при этом вынести саму реализацию в отдельный класс. Таким образом каждая фабрика — это какой-то класс, который инстанцирует свою «рабочую лошадку».
В демо-примере используется интерфейс FactoryInterface, который содержит метод, который должен быть реализован в классах фабрик: ConcreteFactoryA и ConcreteFactoryB. То есть это гарантия, что эти классы будут обязательно содержать нужные методы. В нашем случае это method()
.
Класс ConcreteFactoryA для этого метода будет использовать класс Class1, который может быть уже произвольным. Главное, чтобы родительский класс о нём знал. Поэтому, когда потребуется внести изменения в работу фабрики, то достаточно будет редактировать только Class1. При этом, если у фабрик будет несколько методов (мы их формируем через интерфейс), то можно каждую (конечную) реализацию вынести в другие классы (Class3, Class4 и т.п.).
При желании можно всю реализацию оставить и в самом классе фабрики без использования дополнительных классов.
При этом, заметьте, мы создаём только класс фабрики, а остальные классы спрятаны.
/** * create FactoryA */ $a = new ConcreteFactoryA(); $a->method(); // Class1 method /** * create FactoryB */ $b = new ConcreteFactoryB(); $b->method(); // Class2 method
Зачем нужны фабрики?
В первую очередь для унификации классов, поскольку они наследуются от единого интерфейса. Также мы точно знаем какие методы будут в фабрике, а значит фабрику можно использовать ту, что подходит под конкретную задачу. Вот примерно так:
$type_file = 'zip'; // rar, arj if ($type_file == 'zip') { $f = new ZipFactory(); } elseif ($type_file == 'rar') { $f = new RarFactory(); } else { $f = new OtherFactory(); } $f->pack();
То есть внешне мы работаем только с $f->pack()
, хотя конкретная реализация будет зависеть от используемой фабрики.
Хоть убейте не понимаю, зачем нужны дополнительные классы Class1 и Class2!
Почему реализацию метода method() нельзя организовать в ConcreteFactoryA и ConcreteFactoryB? Зачем вообще в этих классах нужно в конструкторах создавать экземпляры двух других классов, чтобы вызывать методы тех классов!?
В чём прикол?
Тут или пример сильно утрированный или одно из двух!
Пример, конечно утрирован, но сильно приближен к практике. Можно всё реализовать и в ConcreteFactoryA и ConcreteFactoryB, но тогда это будут обычные классы, связанные общим интерфейсом. В реальном проекте используется композиция, которая и делается через Class1 и Class2. Чтобы было проще понять, представьте себе, что у вас совершено раздельные библиотеки (с разными методами) для работы с zip и rar файлами, на которые вы не можете влиять (например вы их используете через композер). Пряча их в фабрику, вы скрываете реализацию, а пользователю оставляете только внешние типовые методы для работы.
Ага, вот теперь понятнее - т.е. если есть сторонние классы с разными методами - вы их оборачиваете в свои классы с одинаковыми методами, чтобы не переписывать свой зависимы от них код. Ок, спасибо.
В примере бы тогда был бы смысл в Class1 и Class2 методы называть по разному, а в фабрике их вызывать - было бы нагляднее и понятнее. А то всё одинаковое и смысл немного теряется.
В данном случае слово «method» следует понимать как «любой метод». :)
А разве это не паттерн фасада?
Нет, паттерн это алгоритм. Многие паттерны скрывают реализацию одних классов от других. Разница только в том, как именно это реализуется.
А если мы используем несколько похожих (однотипных?) фасадов, которые делают похожую задачу (но разная реализация внутри), и всех их объединяет какой-то интерфейс - то эти фасады превращаются в фабрики?
Если фасады однотипные, то наверное есть смысл переделать код на паттерн с использованием абстрактных классов. Это если хотя бы часть функционала повторяется. Или породить от одного интерфейса, чтобы обеспечить совместимость типа.
Но вообще фасад — это обычная композиция, никто не мешает её использовать в любом другом паттерне: фабрики, стратегия и т.п.