Привет, Хабр! Представляю вашему вниманию перевод очередной статьи Design Patterns: Abstract Factory Pattern автора Shubham Zanwar.
Абстрактная фабрика - это порождающий шаблон проектирования. Он используется, когда нам нужно создать семейство похожих продуктов. Давайте для понимания возьмем пример сети пиццерий.
Пиццерия
Представим, что вы глава бизнеса и открываете сеть пиццерий по всему городу. Одной из ваших обязанностей является производство всех основных продуктов (в нашем случае это пиццы и жареный чесночный хлеб), которые будут представлять такие бренды как Домино и Жаровня (назовем их так - прим. перев.).
Есть множество способов как это сделать. Самый простой - создать фабрику по производству пицц каждого бренда и еще похожую фабрику для жаренного хлеба.
Если вы еще не представляете, как работают фабрики, вы можете почитать здесь
Проблема в том, что теперь мы доверяем пользователю выбор правильного типа пиццы и жаренного хлеба, который они хотят. Если они сделают ошибку в приготовлении пиццы Домино с чесночным хлебом Жаровни, ваши заказчики будут в ярости и вы можете потерять контракт с сетями этих брендов.
Не волнуйтесь, есть простой способ.
Вместо создания фабрики для каждого продукта (пиццы или жаренного хлеба), вы сможете создавать фабрику для каждого бренда. Обе фабрики будут производить пиццу и жареный хлеб.
Открыв пиццерию, вы передаете менеджеру фабрику Домино или Жаровню и можете отдохнуть, потому что теперь никто ничего не перепутает.
Давайте посмотрим на код. Перед тем как мы напишем фабрики, создадим сами продукты:
Обычная пицца
type iPizza interface { GetPrice() float64 GetName() string GetToppings() []string}type pizza struct { name string price float64 toppings []string}func (p *pizza) GetName() string { return p.name}func (p *pizza) GetPrice() float64 { return p.price}func (p *pizza) GetToppings() []string { return p.toppings}
Пиццы наших брендов
type pizzaHutPizza struct { pizza}type dominosPizza struct { pizza}
Жареный чесночный хлеб
type iGarlicBread interface { GetPrice() float64 GetName() string}type garlicBread struct { name string price float64}func (g *garlicBread) GetName() string { return g.name}func (g *garlicBread) GetPrice() float64 { return g.price}
И наших брендов
type pizzaHutGarlicBread struct { garlicBread}type dominosGarlicBread struct { garlicBread}
Мы создали оба наших продукта, которые реализуют общий интерфейс, облегчая конечному пользователю их потребление. Вот такой каламбур.
Теперь напишем сами фабрики, сначала общая
type iPizzaFactory interface { createPizza() iPizza createGarlicBread() iGarlicBread}
Теперь наших брендов: Жаровня-фабрика и Домино-фабрика с унифицированной функциональностью
type PizzaHutFactory struct {}func (p *PizzaHutFactory) createPizza(): iPizza { return &pizzaHutPizza{ pizza{ name: "pepperoni", price: 230.3, toppings: []string{"olives", "mozzarella", "pork"}, }, }}func (p *pizzaHutFactory) createGarlicBread() iGarlicBread { return &pizzaHutGarlicBread{ garlicBread{ name: "garlic bread", price: 180.99, }, }}
type dominosFactory struct{}func (d *dominosFactory) createPizza() iPizza { return &dominosPizza{ pizza{ name: "margherita", price: 200.5, toppings: []string{"tomatoes", "basil", "olive oil"}, }, }}func (d *dominosFactory) createGarlicBread() iGarlicBread { return &dominosGarlicBread{ garlicBread{ name: "cheesy bread sticks", price: 150.00, }, }}
Мы можем выбирать любую фабрику и продолжить приготовление пиццы или жаренного хлеба и быть абсолютно уверенными, что любой производный продукт будет соответствующего семейства/бренда.
Мы почти у цели. Давайте обернем это в фабрику, которая будет возвращать нам фабрику по нашему желанию. Сбиты с толку? Тогда еще раз прочитайте предыдущее предложение.
Думайте о наших фабриках как о другом объекте. Основываясь на типе или пиццерии, которые мы хотим открыть (Жаровня или Домино), мы создаем нужную фабрику (просто другой объект). Чтобы автоматически получать эти "объекты", у нас будет своя фабрика.
Этот код поможем вам - Фабрика фабрик
func getPizzaFactory(chain string) (iPizzaFactory, error) { if chain == "P" { return &pizzaHutFactory{}, nil } if chain == "D" { return &dominosFactory{}, nil } return nil, fmt.Errorf("Enter a valid chain type next time")}
Надеюсь, стало понятнее.
Основное, что нужно запомнить: шаблон абстрактная фабрика реализует фабрику фабрик. Внутренние фабрики используются для создания продуктов нужного вида.
Вы можете найти этот код на github
Пока