
Рассмотрим в этой статье несколько наиболее распространенных паттернов проектирования в Golang, дополнив их практическими примерами.
Фасад, Стратегия, Прокси, Адаптер
Паттерн «Фасад»
Фасад — это паттерн проектирования, который предоставляет простой интерфейс для работы с сложной системой. Вместо того чтобы разбираться с множеством деталей и компонентов, мы можем использовать фасад, который берёт на себя всю работу «под капотом». Простыми словами Фасад — это как кнопка «Выполнить всё». Он объединяет несколько действий в одном месте, чтобы тебе было проще.
Пример:
Допустим, у нас есть умный дом. И мы хотим упростить повседневные задачи, например, включение режима «Спокойной ночи». Для этого нужно:
-
Выключить свет.
-
Закрыть шторы.
-
Настроить температуру.
-
Включить сигнализацию.
Делать это вручную долго и неудобно, да и зачем оно надо. Вместо этого можно сделать фасад, который выполнит все действия одной командой.
package main import "fmt" // Подсистема 1: Освещение type Lights struct{} func (l *Lights) Off() { fmt.Println("Свет: выключен") } // Подсистема 2: Шторы type Curtains struct{} func (c *Curtains) Close() { fmt.Println("Шторы: закрыты") } // Подсистема 3: Кондиционер type Thermostat struct{} func (t *Thermostat) SetTemperature(temp int) { fmt.Printf("Кондиционер: Установлена температура %d°C\n", temp) } // Подсистема 4: Сигнализация type Alarm struct{} func (a *Alarm) Activate() { fmt.Println("Сигнализация: активирована") } // Фасад: Умный дом type SmartHomeFacade struct { lights *Lights curtains *Curtains thermostat *Thermostat alarm *Alarm } // Конструктор фасада func NewSmartHomeFacade() *SmartHomeFacade { return &SmartHomeFacade{ lights: &Lights{}, curtains: &Curtains{}, thermostat: &Thermostat{}, alarm: &Alarm{}, } } // Метод для включения режима "Спокойной ночи" func (s *SmartHomeFacade) GoodNightMode() { fmt.Println("Активация режима `Спокойной ночи`...") s.lights.Off() s.curtains.Close() s.thermostat.SetTemperature(20) // Устанавливаем комфортную температуру s.alarm.Activate() fmt.Println("Режим `Спокойной ночи` активирован!") } func main() { // Создаём фасад для умного дома smartHome := NewSmartHomeFacade() // Активируем режим "Спокойной ночи" smartHome.GoodNightMode() }
Пояснение:
Подсистемы:
-
Lights (Освещение) — управляет светом в доме, метод
Off()выключает свет. -
Curtains (Шторы) — управляет шторами, метод
Close()закрывает их. -
Thermostat (Кондиционер) — управляет температурой в доме, метод
SetTemperature(int)устанавливает температуру. -
Alarm (Сигнализация) — управляет сигнализацией, метод
Activate()включает сигнализацию.
Фасад (SmartHomeFacade):
Фасад объединяет все эти подсистемы в один объект, предоставляя клиенту простой способ управлять всем умным домом. Вместо того, чтобы обращаться к каждой подсистеме по отдельности, можно просто использовать фасад.
-
Конструктор
NewSmartHomeFacadeсоздает и инициализирует все подсистемы, а затем объединяет их в одном объекте.
Метод GoodNightMode:
-
Выключает свет, вызывая метод
s.lights.Off(). -
Закрывает шторы, вызывая метод
s.curtains.Close(). -
Устанавливает комфортную температуру (20°C) для кондиционера через
s.thermostat.SetTemperature(20). -
Активирует сигнализацию с помощью
s.alarm.Activate().
Основной код (main):
-
В
mainсоздается объект фасадаsmartHome, который автоматически управляет всеми подсистемами. -
Затем вызывается метод
GoodNightMode(), который активирует режим «Спокойной ночи», выполняя все необходимые действия для подготовки дома к ночному времени.
Паттерн «Стратегия»
Паттерн «Стратегия» — это выбор способа действия из нескольких вариантов. Мы создаём набор алгоритмов (или стратегий), а потом можем переключаться между ними, не меняя основную логику программы.
Представь, что ты идёшь в магазин. У тебя есть 2 варианта:
-
Оплатить картой
-
Оплатить наличными
Магазин предоставляет одинаковую услугу (покупку товара), но ты выбираешь, как заплатить, в зависимости от ситуации.
package main import "fmt" // Интерфейс, который определяет стратегию оплаты type PaymentStrategy interface { Pay(amount float64) } // Стратегия оплаты картой type CardPayment struct{} func (c *CardPayment) Pay(amount float64) { fmt.Printf("Оплата картой: %.2f рублей\n", amount) } // Стратегия оплаты наличными type CashPayment struct{} func (c *CashPayment) Pay(amount float64) { fmt.Printf("Оплата наличными: %.2f рублей\n", amount) } // Контекст, который использует одну из стратегий type Shop struct { paymentStrategy PaymentStrategy } func (s *Shop) SetPaymentStrategy(strategy PaymentStrategy) { s.paymentStrategy = strategy } func (s *Shop) MakePayment(amount float64) { s.paymentStrategy.Pay(amount) } func main() { // Создаем магазин shop := &Shop{} // Платим картой shop.SetPaymentStrategy(&CardPayment{}) shop.MakePayment(1000.50) // Платим наличными shop.SetPaymentStrategy(&CashPayment{}) shop.MakePayment(500.75) }
Пояснение:
-
Интерфейс
PaymentStrategy:-
Это интерфейс, который определяет метод
Pay(amount float64), который будет реализован различными стратегиями оплаты. -
Все стратегии должны реализовывать этот интерфейс, обеспечивая тем самым различное поведение для оплаты.
-
-
Конкретные стратегии оплаты:
-
CardPayment(оплата картой): Реализует методPay(), который выводит сообщение о платеже с картой. -
CashPayment(оплата наличными): Реализует методPay(), который выводит сообщение о платеже наличными.
-
-
Контекст
Shop:-
В классе
Shopхранится ссылка на объект, который реализует интерфейсPaymentStrategy. -
Метод
SetPaymentStrategy(strategy PaymentStrategy)позволяет устанавливать стратегию оплаты. -
Метод
MakePayment(amount float64)вызывает методPay()у установленной стратегии для выполнения оплаты.
-
-
Основная программа (
main):-
Создается объект магазина
shop. -
Сначала устанавливается стратегия оплаты картой с помощью
SetPaymentStrategy(&CardPayment{}), и затем вызывается методMakePayment(), чтобы совершить оплату картой. -
Далее стратегия меняется на оплату наличными с помощью
SetPaymentStrategy(&CashPayment{}), и снова вызываетсяMakePayment()для
-
Паттерн «Прокси»
Паттерн Прокси — это посредник, который контролирует доступ к другому объекту. Он выполняет действия до или после обращения к реальному объекту, такие как проверка прав доступа, кэширование, логирование и т. д.
Представим, что у нас есть объект, который подключается к базе данных, и мы хотим контролировать доступ с помощью прокси. Прокси будет проверять, есть ли у пользователя права на подключение, прежде чем передать запрос реальному объекту.
package main import "fmt" // Интерфейс для работы с базой данных type Database interface { Connect() string Query(query string) string } // Реальная база данных, которая выполняет запросы type RealDatabase struct{} func (db *RealDatabase) Connect() string { return "Подключение к реальной базе данных..." } func (db *RealDatabase) Query(query string) string { return fmt.Sprintf("Запрос к базе данных: %s", query) } // Прокси для базы данных, который проверяет права доступа пользователя type DatabaseProxy struct { realDatabase Database userRole string // Роль пользователя (например, "admin", "user", "guest") } func (proxy *DatabaseProxy) Connect() string { // Прокси проверяет права доступа if proxy.userRole != "admin" { return "Ошибка доступа: недостаточно прав для подключения к базе данных." } // Передаем запрос реальной базе данных return proxy.realDatabase.Connect() } func (proxy *DatabaseProxy) Query(query string) string { // Прокси проверяет права доступа if proxy.userRole != "admin" { return "Ошибка доступа: недостаточно прав для выполнения запроса." } // Передаем запрос реальной базе данных return proxy.realDatabase.Query(query) } func main() { // Создаем реальную базу данных realDB := &RealDatabase{} // Создаем прокси для базы данных с ролью "admin" adminProxy := &DatabaseProxy{ realDatabase: realDB, userRole: "admin", // Этот пользователь имеет доступ } // Попытка подключиться и выполнить запрос с правами администратора fmt.Println(adminProxy.Connect()) fmt.Println(adminProxy.Query("SELECT * FROM users")) // Создаем прокси для базы данных с ролью "guest" guestProxy := &DatabaseProxy{ realDatabase: realDB, userRole: "quest", // У этого пользователя нет доступа } // Попытка подключиться и выполнить запрос с правами гостя fmt.Println(guestProxy.Connect()) fmt.Println(guestProxy.Query("SELECT * FROM users")) }
Пояснение:
-
Интерфейс
Database: Это общая форма для работы с базой данных. Он определяет методы для подключения и выполнения запросов. -
Реальная база данных
RealDatabase: Это структура, которая реализует интерфейсDatabase. Она выполняет реальные действия по подключению и выполнению запросов. -
Прокси
DatabaseProxy: Это структура, которая тоже реализует интерфейсDatabase, но добавляет проверку прав доступа. В прокси хранится информация о роли пользователя, и если у пользователя нет прав (например, роль «guest»), то доступ к базе данных будет ограничен. -
Основная программа:
-
Сначала создается реальная база данных и прокси с правами администратора, которые могут подключаться и делать запросы.
-
Затем создается прокси с правами гостя, который не может подключиться и выполнять запросы, так как его роль не имеет доступа.
-
Паттерн «Адаптер»
Паттерн Адаптер — это паттерн проектирования, который преобразует интерфейс одного объекта в интерфейс, ожидаемый другим объектом. Он позволяет несовместимым интерфейсам работать вместе.
У нас есть зарядное устройство с разъемом USB-C, а телефон имеет разъем Lightning. В жизни я думаю сразу понятно, как это можно сделать. Чтобы подключить их, нам нужен адаптер, который будет преобразовывать разъем USB-C в Lightning.
package main import "fmt" // Интерфейс для устройств с разъемом Lightning type LightningPhone interface { ChargeWithLightning() } // Реальный телефон с разъемом Lightning type iPhone struct{} func (i *iPhone) ChargeWithLightning() { fmt.Println("iPhone заряжается через Lightning!") } // Интерфейс для зарядных устройств с разъемом USB-C type USBCCharger interface { ChargeWithUSB_C() } // Реализация зарядного устройства с USB-C // Это существующий класс, который мы хотим использовать // для устройств с разъемом Lightning через адаптер // Зарядное устройство с разъемом USB-C type USBCharger struct{} func (u *USBCharger) ChargeWithUSB_C() { fmt.Println("Устройство получает заряд через USB-C!") } // Адаптер, который позволяет заряжать Lightning-устройства // с использованием USB-C зарядного устройства type USBToLightningAdapter struct { usbCharger USBCCharger } func (a *USBToLightningAdapter) ChargeWithLightning() { fmt.Println("Адаптер преобразует заряд USB-C в Lightning...") a.usbCharger.ChargeWithUSB_C() } func main() { // Создаем зарядное устройство с USB-C usbCharger := &USBCharger{} // Создаем адаптер, который будет использовать USB-C зарядное устройство для Lightning adapter := &USBToLightningAdapter{usbCharger: usbCharger} // Создаем iPhone с разъемом Lightning iphone := &iPhone{} // Заряжаем iPhone напрямую через его интерфейс fmt.Println("Заряжаем iPhone напрямую:") iphone.ChargeWithLightning() // Заряжаем iPhone через адаптер, используя USB-C зарядное устройство fmt.Println("\nЗаряжаем iPhone через USB-C с использованием адаптера:") adapter.ChargeWithLightning() }
Пояснение:
-
В функции
mainсоздается объект зарядного устройства usbCharger, который использует разъем USB-C. -
Создается объект adapter типа USBToLightningAdapter, который принимает объект usbCharger. Этот адаптер преобразет интерфейс USB-C в интерфейс Lightning, что позволяет использовать зарядку с USB-C для устройства с разъемом Lightning.
-
adapter.ChargeWithLightning()— когда мы уadapterвызываем методChargeWithLightning, происходит следующее: -
Адаптер вызывает
ChargeWithUSB_C()на объекте usbCharger, который выводит сообщение, что зарядка идет через USB-C. -
Однако перед этим адаптер выводит сообщение о том, что он преобразует USB-C в Lightning. Таким образом, адаптер помогает подключить несовместимые устройства (разъемы USB-C и Lightning) и дает возможность зарядить iPhone через USB-C зарядку.
Мы рассмотрели в этой статье 4 наиболее распространенных паттернов проектирования в Golang. Фасад, Стратегия, Прокси, Адаптер
Спасибо за обратную связь. Всего доброго!
ссылка на оригинал статьи https://habr.com/ru/articles/875794/
Добавить комментарий