Дисклеймер
Сразу хочу снять с себя всю ответственность за пояснения по тексту, я буду описывать отличия и особенности между паттернами в рамках своего понимания паттернов. Мне конечно пришлось проработать много литературы на этот счёт, но есть ненулевая вероятность, что моё понимание предмета и его истинное значение могут не совпадать. Поэтому не надо закидывать меня кАкушками, но за конструктивную критику в комментариях буду благодарен и готов вносить правки.
Полный перечень паттернов описан в отдельном посте.
Там же общие слова и мысли о необходимости паттернов в жизни программиста.
Абстракнтая фабрика — порождающий паттерн проектирования, нацеленный на создание семейства объектов, не привязываясь к конкретным классам создаваемых объектов.
В отличии от реализации фабричного метода, где объект помимо создания других объектов самим методом, может выполнять какие-то иные действия, абстрактная фабрика занимается только задачами создания объектов определённого семейства.
Словесный пример
Допустим, мы пишем систему аренды транспорта или извоза, т.е. типа такси. Заказчик у нас серьёзный и сдаёт в аренду различные типы автомобилей, для упрощения картины возьмём легковые, пассажирские, например автобусы, и грузовые машины. К тому же у заказчика есть придурь, у него есть градация по маркам, а точнее, для определённого региона, у него строго определённая марка. К примеру: в России — Форд, в Белоруссии — Рено, а в Германии — Мерседес. Очень крупный заказчик.
Итого у нас получается матрица из марок авто и категории авто.
Мы должны понимать, что различные типы авто предназначены для различного использования. Легковое авто чаще берётся в аренду в личное пользование, автобусы для перевозки большого количества людей, а грузовики перевозят грузы из пункта «А», в пункт «Б».
Чтобы не погрязнуть в абстракциях, давай выделим какие-то уникальные методы для каждого типа транспорта. Для легкового автомобиля нужен метод, определяющий текущую стоянку, чтобы заказчик понимал куда ему идти за машиной. Для автобуса уникальным свойством является его вместимость и, допустим, комфортабельность. А для грузовика важна грузоподъемность. Конечно, это не совсем про методы, но пока мне лень развивать идею. Поэтому.
Кодовое выражение
Для начала определим для каждого типа транспорта интерфейс
У легкового будет выглядеть так
interface Car { public function getParking(); }
У автобуса
interface Bus { public function getMaxPassengers(); public function getComfortability(); }
А у грузовика
interface Truck { public function getCarrying(); }
Но мы же с тобой определили, что важно, чтобы в каждой стране создавались только определённая марка. И, соответственно, от каждого производителя у нас будет свой класс, создающий нужный тип транспорта. Вот мы и пришли к фабрике. Для начала сам интерфейс абстрактной фабрики
interface TransportFactory { public function createCar(): Car; public function createBus(): Bus; public function createTruck(): Truck; }
А теперь реализуем его для каждой марки. Как видно, крайне желательно, чтобы каждый метод на выходе давал сущность, реализующую одинаковый интерфейс. Тогда пользовательский код не сможет прозрачно и универсально работать с любым инстансом.
Конечно, я не буду описывать все реализации фабрики, обойдёмся одним примером
class FordFactory implements TransportFactory { public function createCar(): Car { return new FordCar(); } public function createBus(): Bus { return new FordBus(); } public function createTruck(): Truck { return new FordTruck(); } }
И тут ты вправе воскликнуть: «Ой-ой-ой! Но как же так?! Метод createCar должен возвращать Car, а возвращает какой-то непонятны FordCar?» Это справедливый вопрос, и отвечу я тебе на него песней, точнее кодом.
/** * Class FordCar * * Реализует интерфейс Car */ class FordCar implements Car { public function getParking() { // TODO: Implement getParking() method. } }
Вот видишь, сила интерфейсов! Именно они в большинстве порождающих паттернов играют значительную роль, не пренебрегай ими. Так же опишем остальные реализации для форда
/** * Class FordBus * * Реализует интерфейс Bus */ class FordBus implements Bus { public function getMaxPassengers() { // TODO: Implement getMaxPassengers() method. } public function getComfortability() { // TODO: Implement getComfortability() method. } } /** * Class FordTruck * * Реализует интерфейс Truck */ class FordTruck implements Truck { public function getCarrying() { // TODO: Implement getCarrying() method. } }
Итого
На выходе получаем очень стройную архитектуру, абстрагируемся от конкретных классов за интерфейсами. Но всё это не бесплатно. Очень много слоёв абстракции и итоговых классов. Иногда это только на бумаге красиво выглядит, а когда реально начинаешь прорабатывать, то с непривычки мозг начинает пухнуть. И самым серьёзным недостатком я бы выделил необходимость существования всех типов транспорта для каждого региона, если описывать проблему отталкиваясь от нашего примера. Т.е. если есть условие, что где-то не может быть одного типа продукта, то придётся либо интерфейсы описывать и на уровне клиентского кода делать проверки, либо городить огород из костылей.
P.S.
А ты заметил, что этот паттерн неплохо перекликается с фабричным методом? Ага-ага, каждый метод Абстрактной фабрики, это частный случай фабричного метода.