Фабричный метод
Суть паттерна
Фабричный метод — это порождающий паттерн проектирования, который определяет общий интерфейс для создания объектов в суперклассе, позволяя подклассам изменять тип создаваемых объектов.
Представьте, что вы создаёте программу управления грузовыми перевозками. Сперва вы рассчитываете перевозить товары только на автомобилях. Поэтому весь ваш код работает с объектами класса Грузовикs.
В какой-то момент ваша программа становится настолько известной, что морские перевозчики выстраиваются в очередь и просят добавить поддержку морской логистики в программу.
Отличные новости, правда?! Но как насчёт кода? Большая часть существующего кода жёстко привязана к классам Грузовиков. Чтобы добавить в программу классы морских Судов, понадобится перелопатить всю программу. Более того, если вы потом решите добавить в программу ещё один вид транспорта, то всю эту работу придётся повторить.
В итоге вы получите ужасающий код, наполненный условными операторами, которые выполняют то или иное действие, в зависимости от класса транспорта.
Решение
Паттерн Фабричный метод предлагает создавать объекты не напрямую, используя оператор new, а через вызов особого фабричного метода. Не пугайтесь, объекты всё равно будут создаваться при помощи new, но делать это будет фабричный метод.
На первый взгляд, это может показаться бессмысленным: мы просто переместили вызов конструктора из одного конца программы в другой. Но теперь вы сможете переопределить фабричный метод в подклассе, чтобы изменить тип создаваемого продукта.
Чтобы эта система заработала, все возвращаемые объекты должны иметь общий интерфейс. Подклассы смогут производить объекты различных классов, следующих одному и тому же интерфейсу.
Например, классы Грузовик и Судно реализуют интерфейс Транспорт с методом доставить. Каждый из этих классов реализует метод по-своему: грузовики везут грузы по земле, а суда — по морю. Фабричный метод в классе ДорожнойЛогистики вернёт объект-грузовик, а класс МорскойЛогистики — объект-судно.
Для клиента фабричного метода нет разницы между этими объектами, так как он будет трактовать их как некий абстрактный Транспорт. Для него будет важно, чтобы объект имел метод доставить, а как конкретно он работает — не важно.
Пример
Рассмотрим пример где фабрику мороженого можно использовать для создания мороженого разных вкусов. Давайте посмотрим, как мы можем применить паттерн фабрики в этом примере.
1class IceCreamFactory {2 constructor() {3 this.createIcecream = function(flavor) {4 let iceCream;5 if (flavor === 'chocolate'){6 iceCream = new Chocolate();7 }8 else if (flavor === 'mint'){9 iceCream = new Mint();10 }11 else if (flavor === 'strawberry'){12 iceCream = new Strawberry();13 }14 return iceCream;15 };16 }17}1819class Chocolate {20 constructor() {21 this.icecreamFlavor = "chocolate";22 this.message = function() {23 return `You chose the ${this.icecreamFlavor} flavor.`;24 };25 }26}2728class Mint {29 constructor() {30 this.icecreamFlavor = "mint";31 this.message = function() {32 return `You chose the ${this.icecreamFlavor} flavor.`;33 };34 }35}3637class Strawberry{38 constructor() {39 this.icecreamFlavor = "strawberry";40 this.message = function() {41 return `You chose the ${this.icecreamFlavor} flavor.`;42 };43 }44}4546// creating objects47const iceCreamfactory = new IceCreamFactory();4849const chocolate = iceCreamfactory.createIcecream('chocolate');50const mint = iceCreamfactory.createIcecream('mint');51const strawberry = iceCreamfactory.createIcecream('strawberry');5253console.log(chocolate.message());54console.log(mint.message());55console.log(strawberry.message());
В приведенном выше примере мы создали фабрику IceCreamFactory. В его конструкторе есть функция createIcecream, которая принимает тип параметра. В зависимости от вкуса он создает экземпляр объекта соответствующего класса. Например, если ароматизатор шоколадный, он создает экземпляр объекта класса Chocolate (строка 6). То же самое происходит с ароматом мяты или клубники (строки 9 и 12).
В приведенном выше примере показано, как можно использовать один общий «фабричный метод» для создания различных объектов с похожими характеристиками.
Когда использовать?
- Когда тип требуемых объектов невозможно предугадать заранее.
- Когда необходимо создать несколько объектов с одинаковыми характеристиками.
Преимущества
- Избавляет класс от привязки к конкретным классам продуктов.
- Выделяет код производства продуктов в одно место, упрощая поддержку кода.
- Упрощает добавление новых продуктов в программу.
- Реализует принцип открытости/закрытости.
Недостатки
- Может привести к созданию больших параллельных иерархий классов, так как для каждого класса продукта надо создать свой подкласс создателя.
Более подробнее почитать о паттерне фабричный метод можно тут