Введение в паттерны проектирования
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 85 из 107.
Паттерны проектирования — это многократно используемые решения распространенных проблем, возникающих при проектировании программного обеспечения. Это не код, который вы копируете напрямую, а скорее шаблоны, определяющие структуру ваших типов и их взаимодействие для решения конкретных задач.
Паттерны проектирования обычно делятся на три категории:
| Категория | Назначение | Примеры |
|---|---|---|
| Порождающие | Механизмы создания объектов | Singleton, Factory, Builder |
| Структурные | Компоновка типов в более крупные структуры | Adapter, Decorator, Composite |
| Поведенческие | Взаимодействие между объектами | Observer, Strategy, Command |
В Go паттерны проектирования выглядят иначе, чем в традиционных ООП-языках.
Без классов или наследования Go реализует паттерны, используя интерфейсы, встраивание структур и композицию. Например, там, где Java могла бы использовать абстрактный класс, Go использует интерфейс. Там, где C++ мог бы использовать наследование, Go использует встраивание.
Изучение паттернов помогает вам распознавать распространенные проблемы и эффективно обсуждать решения с другими разработчиками. Когда кто-то упоминает «используйте здесь паттерн Strategy», вся команда понимает подход без долгих объяснений.
В следующих уроках вы реализуете несколько фундаментальных паттернов на Go, начиная с порождающих паттернов, таких как Singleton и Factory, а затем перейдете к поведенческим паттернам, таким как Observer и Strategy.
Задание
ЛегкоДавайте создадим простую систему уведомлений, которая демонстрирует, как паттерны проектирования помогают организовать код! Вы создадите структуру, которую в будущих уроках можно будет легко расширить с помощью таких паттернов, как Наблюдатель (Observer) или Стратегия (Strategy), но пока сосредоточьтесь на настройке чистых интерфейсов и типов, следующих принципам ООП.
Вы организуете свой код в трех файлах:
notifier.go: Определите основной интерфейс и типы для вашей системы уведомлений.Создайте интерфейс
Notifierс единственным методомSend(message string) string, который возвращает строку с подтверждением.Реализуйте два конкретных типа уведомлений:
EmailNotifierс полемAddress— его методSendвозвращаетEmail to [address]: [message]SMSNotifierс полемPhoneNumber— его методSendвозвращаетSMS to [phone]: [message]
dispatcher.go: Создайте диспетчер, который работает с любым уведомлением через интерфейс.Создайте структуру
NotificationDispatcher, которая содержит срез (slice) интерфейсовNotifier. Добавьте следующие методы:AddNotifier(n Notifier)для регистрации уведомленияBroadcast(message string) []string, который отправляет сообщение через все зарегистрированные уведомления и возвращает срез всех строк с подтверждением
Также создайте конструктор
NewNotificationDispatcher(), который возвращает инициализированный диспетчер.main.go: Настройте уведомления и разошлите сообщения.Прочитайте количество уведомлений, которые нужно создать. Для каждого уведомления прочитайте его тип (
emailилиsms) и место назначения (адрес или номер телефона). Добавьте каждое из них в диспетчер.Затем прочитайте сообщение для рассылки. Отправьте его через все уведомления и выведите каждое подтверждение на новой строке.
Будут предоставлены следующие входные данные:
- Строка 1: Количество уведомлений
- Для каждого уведомления: тип (
emailилиsms), затем место назначения - Последняя строка: Сообщение для рассылки
Например, для входных данных:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightВаш вывод должен быть:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightИ для входных данных:
2
sms
555-9999
sms
555-0000
Meeting canceledВаш вывод должен быть:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledЭта структура демонстрирует, как интерфейсы обеспечивают гибкость и расширяемость дизайна. Диспетчер ничего не знает о конкретных типах уведомлений — он только знает, что они могут выполнять метод Send. Это фундамент, на котором строятся такие паттерны, как Наблюдатель и Стратегия!
Шпаргалка
Паттерны проектирования — это переиспользуемые решения распространенных проблем в проектировании программного обеспечения. Они представляют собой шаблоны, которые определяют структуру типов и их взаимодействие.
Паттерны проектирования делятся на три категории:
| Категория | Назначение | Примеры |
|---|---|---|
| Порождающие (Creational) | Механизмы создания объектов | Singleton, Factory, Builder |
| Структурные (Structural) | Компоновка типов в более крупные структуры | Adapter, Decorator, Composite |
| Поведенческие (Behavioral) | Взаимодействие между объектами | Observer, Strategy, Command |
В Go паттерны проектирования реализуются с использованием интерфейсов, встраивания структур (struct embedding) и композиции вместо классов и наследования. Там, где традиционные ООП-языки используют абстрактные классы, Go использует интерфейсы. Там, где они используют наследование, Go использует встраивание.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать количество уведомлений
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Создать новый диспетчер
dispatcher := NewNotificationDispatcher()
// Прочитать каждый уведомитель и добавить в диспетчер
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: Создать соответствующий уведомитель на основе типа
// и добавить его в диспетчер
// Подсказка: Проверьте, является ли notifierType "email" или "sms"
}
// Прочитать сообщение для рассылки
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: Разослать сообщение и вывести каждое подтверждение
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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 Pool