Что это такое
Паттерн Builder (строитель) собирает сложный объект шаг за шагом. Вместо одного конструктора с длинным и запутанным списком аргументов строитель предлагает небольшие именованные шаги — и завершающий Build(), который возвращает готовый объект.
В Go шаги обычно возвращают сам строитель, поэтому вызовы объединяются в цепочку и читаются как понятное предложение.
Пример: сборка пиццы
Сравните NewPizzaBuilder("большая").Cheese().Topping("грибы") с конструктором вида NewPizza("большая", true, []string{...}) — строитель сам говорит, что означает каждое значение, а необязательные части просто пропускаются.
Шаги, объединяемые в цепочку
У каждого шага есть имя, и каждый шаг необязателен: маленькая пицца пропускает их все. Никакой каши из аргументов true, nil, false.
Вывод: программа на Go и захваченный вывод
package main
import "fmt"
type Pizza struct {
size string
cheese bool
toppings []string
}
type PizzaBuilder struct {
pizza Pizza
}
func NewPizzaBuilder(size string) *PizzaBuilder {
return &PizzaBuilder{pizza: Pizza{size: size}}
}
func (b *PizzaBuilder) Cheese() *PizzaBuilder {
b.pizza.cheese = true
return b
}
func (b *PizzaBuilder) Topping(t string) *PizzaBuilder {
b.pizza.toppings = append(b.pizza.toppings, t)
return b
}
func (b *PizzaBuilder) Build() Pizza {
return b.pizza
}
func main() {
big := NewPizzaBuilder("большая").
Cheese().
Topping("грибы").
Topping("оливки").
Build()
small := NewPizzaBuilder("маленькая").Build()
fmt.Printf("%s пицца, сыр: %v, добавки: %v\n", big.size, big.cheese, big.toppings)
fmt.Printf("%s пицца, сыр: %v, добавки: %v\n", small.size, small.cheese, small.toppings)
}
Когда его применять
Применяйте его, когда у объекта много необязательных частей или когда сборка состоит из настоящих шагов, которым стоит дать имена. Запросы, выборки данных и конфигурации — классическая территория строителя.
Откажитесь от него для простых структур: литерал структуры Go Pizza{size: "большая"} с именованными полями и так уже неплохой маленький строитель для лёгких случаев.
Плюсы и минусы
Плюсы: читаемая сборка; необязательные части без огромных списков аргументов; объект можно проверить один раз — в Build().
Минусы: появляется второй тип, который нужно писать и поддерживать; это излишество там, где литерал структуры и так читается хорошо.