Шаблоны проектирования в Go — Абстрактная Фабрика +2


AliExpress RU&CIS

Привет, Хабр! Представляю вашему вниманию перевод очередной статьи «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

Пока




Комментарии (11):

  1. DmitriyTitov
    /#22355046 / +1

    В этой статье вы программируете на Java или C#, при этом зачем-то используя синтаксис Go.
    Это касается и подхода, и имён (iPizza, GetName() — строго против рекомендаций).
    Не то чтобы так нельзя, но так не принято.

    • Zuy
      /#22356076

      А можете раскрыть именно насчёт подхода? Интересно, как принято делать тоже самое в Go.

      • DmitriyTitov
        /#22356234 / +1

        Могу. В Go так делать вообще не принято.
        Go — процедурный язык с элементами ООП. Не принято, к примеру, возвращать интерфейсы. Да и сама абстракция из общих соображений не приветствуется.
        Короче говоря: я бы такой код на этапе рецензирования отклонил без очень убедительных причин.

        • Zuy
          /#22356624

          Может подскажете, что почитать по поводу стилю написания программ в Go? Все что я нахожу описывает синтаксис но не стиль.
          У меня большой опыт в С/C++ и сейчас, когда пишу на Go, как-то все похоже на тот же самый С только с другим синтаксисом.

          • DmitriyTitov
            /#22356638 / +1

            Собственно не так много всего: Effective Go, книга Donovan, Kernighan.
            Ещё что-то тут: github.com/golang/go/wiki/CodeReviewComments
            Ну и всё, пожалуй, из однозначно полезного и общепризнанного. Дальше всякие статьи и тут уже слушать или нет автора — решать вам.
            Ещё выступления Роба Пайка отличные. Вот про идиоматику: www.youtube.com/watch?v=PAAkCSZUG1c

  2. Zuy
    /#22356062 / +1

    А чем объясняется выбор типа float64 для цены?

    • oxidmod
      /#22356088 / +4

      Планирует заработать на округлениях =)

  3. saluev
    /#22356470 / +1

    Обожаю объяснение паттернов программирования на животных и пиццериях.

    (Нет.)

  4. gotz
    /#22359278

    Программиста, который в Go пишет так, как писали в Java много-много лет назад — сразу уволить за профнепригодность :)