Паттерн Builder в Go
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 80 из 107.
Паттерн Строитель (Builder Pattern) отделяет конструирование сложного объекта от его представления. В отличие от паттерна Функциональные опции (Functional Options), Строитель использует специальную структуру строителя с методами, которые настраивают объект шаг за шагом.
Вот как реализовать строитель для HTTP-запроса:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}
func (b *RequestBuilder) Build() *Request {
return b.request
}Каждый метод возвращает указатель на строитель, что позволяет использовать цепочку вызовов методов. Метод Build() завершает процесс и возвращает сконструированный объект:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()Паттерн Строитель (Builder) отлично подходит в тех случаях, когда объекты требуют нескольких шагов настройки или когда вы хотите обеспечить определенную последовательность конструирования. Он особенно полезен для создания неизменяемых объектов или когда сам процесс конструирования требует валидации.
Задание
ЛегкоДавайте создадим компоновщик сообщений электронной почты, используя паттерн Строитель (Builder Pattern)! Сообщения электронной почты имеют множество необязательных компонентов — получатели, копии (CC), тема, тело письма, вложения — что делает их идеальным кандидатом для пошагового конструирования с использованием цепочки методов (method chaining).
Вы организуете свой код в двух файлах:
email.go: Определите структуру электронного письма и его строителя.Создайте структуру
Emailсо следующими полями:from(string)to(срез строк)cc(срез строк)subject(string)body(string)
Создайте структуру
EmailBuilder, которая содержит указатель на конструируемый объект Email. Реализуйте функциюNewEmailBuilder(), которая возвращает новый строитель с инициализированным объектом Email (пустые срезы дляtoиcc).Добавьте следующие методы строителя, каждый из которых возвращает
*EmailBuilderдля создания цепочки:From(address string)- устанавливает отправителяTo(address string)- добавляет получателя в срезtoCC(address string)- добавляет получателя в срезccSubject(subject string)- устанавливает тему письмаBody(body string)- устанавливает тело письма
Добавьте метод
Build(), который возвращает сконструированный*Email.Наконец, добавьте метод
Summary()для структуры Email, который возвращает строку в следующем формате:From: [from] To: [comma-separated to addresses] CC: [comma-separated cc addresses or "none"] Subject: [subject] Body: [body]main.go: Создавайте и отображайте сообщения электронной почты с помощью строителя.Считайте адрес отправителя, затем считайте количество получателей и каждый адрес получателя. Далее считайте количество получателей копии (CC) и каждый адрес CC. Наконец, считайте тему и тело письма.
Используйте EmailBuilder с цепочкой методов для создания электронного письма, а затем выведите его сводку (summary).
Будут предоставлены следующие входные данные:
- Строка 1: Адрес отправителя
- Строка 2: Количество получателей
- Следующие строки: Каждый адрес получателя
- Следующая строка: Количество получателей копии (CC)
- Следующие строки: Каждый адрес CC
- Следующая строка: Тема
- Последняя строка: Тело письма
Например, при вводе:
alice@company.com
2
bob@company.com
carol@company.com
1
manager@company.com
Weekly Report
Please find the weekly report attached.Ваш вывод должен быть:
From: alice@company.com
To: bob@company.com, carol@company.com
CC: manager@company.com
Subject: Weekly Report
Body: Please find the weekly report attached.И при вводе:
support@service.com
1
customer@email.com
0
Your Ticket Update
Your support ticket has been resolved.Ваш вывод должен быть:
From: support@service.com
To: customer@email.com
CC: none
Subject: Your Ticket Update
Body: Your support ticket has been resolved.Обратите внимание, как строитель позволяет добавлять нескольких получателей по одному за раз через повторяющиеся вызовы To(), и как этот паттерн делает процесс конструирования чистым и читаемым даже при наличии множества необязательных полей.
Шпаргалка
Паттерн Строитель (Builder Pattern) отделяет конструирование сложного объекта от его представления, используя специальную структуру-строитель с методами, которые настраивают объект шаг за шагом.
Базовая структура:
type Request struct {
method string
url string
headers map[string]string
body string
}
type RequestBuilder struct {
request *Request
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
request: &Request{
headers: make(map[string]string),
},
}
}Методы строителя возвращают указатель на строитель, чтобы обеспечить цепочку вызовов (method chaining):
func (b *RequestBuilder) Method(m string) *RequestBuilder {
b.request.method = m
return b
}
func (b *RequestBuilder) URL(u string) *RequestBuilder {
b.request.url = u
return b
}
func (b *RequestBuilder) Header(key, value string) *RequestBuilder {
b.request.headers[key] = value
return b
}Метод Build() завершает процесс и возвращает сконструированный объект:
func (b *RequestBuilder) Build() *Request {
return b.request
}Использование с цепочкой вызовов:
req := NewRequestBuilder().
Method("POST").
URL("https://api.example.com").
Header("Content-Type", "application/json").
Build()Паттерн Строитель полезен, когда объекты требуют нескольких этапов настройки, когда необходимо соблюдение определенной последовательности конструирования, для создания неизменяемых объектов или когда процесс конструирования требует валидации.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать адрес отправителя
from, _ := reader.ReadString('\n')
from = strings.TrimSpace(from)
// Прочитать количество получателей
toCountStr, _ := reader.ReadString('\n')
toCount, _ := strconv.Atoi(strings.TrimSpace(toCountStr))
// Прочитать адрес каждого получателя
toAddresses := make([]string, toCount)
for i := 0; i < toCount; i++ {
addr, _ := reader.ReadString('\n')
toAddresses[i] = strings.TrimSpace(addr)
}
// Прочитать количество получателей копии (CC)
ccCountStr, _ := reader.ReadString('\n')
ccCount, _ := strconv.Atoi(strings.TrimSpace(ccCountStr))
// Прочитать каждый адрес копии (CC)
ccAddresses := make([]string, ccCount)
for i := 0; i < ccCount; i++ {
addr, _ := reader.ReadString('\n')
ccAddresses[i] = strings.TrimSpace(addr)
}
// Прочитать тему письма
subject, _ := reader.ReadString('\n')
subject = strings.TrimSpace(subject)
// Прочитать текст письма
body, _ := reader.ReadString('\n')
body = strings.TrimSpace(body)
// TODO: Используйте EmailBuilder с цепочкой методов для создания электронного письма
// Начните с NewEmailBuilder(), затем последовательно вызовите From(), To(), CC(), Subject(), Body()
// В конце вызовите Build(), чтобы получить Email
// TODO: Выведите краткую информацию о письме, используя метод Summary()
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool12Продвинутые концепции ООП
Паттерн функциональных опцийПаттерн Builder в GoЦепочки вызовов методовПсевдонимы и определения типовОсновы рефлексииОбзор генерации кода