Паттерны
С какими паттернами проектирования знакомы?
Основные паттерны проектирования:
Порождающие паттерны:
-
Singleton (Одиночка):
- Гарантирует, что у класса есть только один экземпляр и предоставляет к нему глобальную точку доступа.
-
Factory Method (Фабричный метод):
- Определяет интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемых объектов.
-
Abstract Factory (Абстрактная фабрика):
- Предоставляет интерфейс для создания семейств взаимосвязанных объектов, не специфицируя их конкретные классы.
-
Builder (Строитель):
- Разделяет создание сложного объекта от его представления, позволяя строить объект пошагово.
-
Prototype (Прототип):
- Создает объекты на основе существующего экземпляра через клонирование.
Структурные паттерны:
-
Adapter(Адаптер):- Позволяет объектам с несовместимыми интерфейсами работать вместе.
-
Bridge(Мост):- Разделяет абстракцию и её реализацию, чтобы они могли изменяться независимо.
-
Composite(Компоновщик):- Компонует объекты в древовидные структуры для представления иерархий часть-целое.
-
Decorator(Декоратор):- Добавляет новой функциональности объекту динамически.
-
Facade(Фасад):- Предоставляет унифицированный интерфейс к набору интерфейсов в подсистеме.
-
Flyweight(Приспособленец):- Позволяет вместить большее количество объектов, разделяя общий внутренний объект.
-
Proxy(Заместитель):- Предоставляет суррогатный объект, контролирующий доступ к другому объекту.
Поведенческие паттерны:
-
Chain of Responsibility(Цепочка обязанностей):- Передает запрос по цепочке обработчиков, пока один из них не обработает запрос.
-
Command(Команда):- Инкапсулирует запрос в виде объекта, позволяя параметризовать клиентами с различными запросами.
-
Interpreter(Интерпретатор):- Определяет грамматику языка и интерпретирует предложения этого языка.
-
Iterator(Итератор):- Предоставляет способ последовательного доступа ко всем элементам коллекции без раскрытия её внутреннего представления.
-
Mediator(Посредник):- Определяет объект, который инкапсулирует способ взаимодействия множества объектов.
-
Memento(Хранитель):- Сохраняет и восстанавливает внутреннее состояние объекта, не нарушая инкапсуляции.
-
Observer(Наблюдатель):- Определяет зависимость один ко многим между объектами, так что при изменении состояния одного объекта все зависимые уведомляются и обновляются.
-
State(Состояние):- Позволяет объекту изменять свое поведение при изменении его внутреннего состояния.
-
Strategy(Стратегия):- Определяет семейство алгоритмов, инкапсулирует их и делает их взаимозаменяемыми.
-
Template Method(Шаблонный метод):- Определяет скелет алгоритма в методе, оставляя определенные шаги подклассам.
-
Visitor(Посетитель):- Позволяет добавлять новые операции к объектам, не изменяя их классы.
Расскажите про паттерны Builder (Строитель), Factory (Фабрика), Closer (Доводчик), Singleton (Одиночка)
Примеры паттернов проектирования:
Builder (Строитель)
Цель: Разделить процесс создания сложного объекта от его представления, позволяя создавать разные представления с помощью одного и того же процесса построения.
Пример:
-
Используется для создания объектов с множеством опций или конфигураций.
-
Включает в себя отдельный объект-строитель, который поэтапно собирает конечный объект.
Пример на Go:
type House struct {
Windows string
Doors string
}
type HouseBuilder struct {
house *House
}
func NewHouseBuilder() *HouseBuilder {
return &HouseBuilder{house: &House{}}
}
func (b *HouseBuilder) SetWindows(windows string) *HouseBuilder {
b.house.Windows = windows
return b
}
func (b *HouseBuilder) SetDoors(doors string) *HouseBuilder {
b.house.Doors = doors
return b
}
func (b *HouseBuilder) Build() *House {
return b.house
}
// Использование
builder := NewHouseBuilder()
house := builder.SetWindows("Wooden").SetDoors("Metal").Build()
Factory (Фабрика)
Цель: Предоставить интерфейс для создания объектов, но позволяет подклассам изменять тип создаваемых объектов.
Пример:
-
Позволяет создавать объекты без указания точного класса создаваемого объекта.
-
Упрощает создание объектов и улучшает поддерживаемость кода.
Пример на Go:
type Animal interface {
Speak() string
}
type Dog struct{}
func (d Dog) Speak() string { return "Woof" }
type Cat struct{}
func (c Cat) Speak() string { return "Meow" }
func AnimalFactory(animalType string) Animal {
switch animalType {
case "dog":
return Dog{}
case "cat":
return Cat{}
default:
return nil
}
}
// Использование
animal := AnimalFactory("dog")
fmt.Println(animal.Speak()) // Вывод: Woof
Singleton (Одиночка)
Цель: Гарантировать, что у класса есть только один экземпляр, и предоставить к нему глобальную точку доступа.
Пример:
- Полезен, когда необходимо контролировать доступ к какому-либо ресурсу, например, к базе данных или к файлу конфигурации.
Пример на Go:
type singleton struct{}
var instance *singleton
var once sync.Once
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
// Использование
s1 := GetInstance()
s2 := GetInstance()
fmt.Println(s1 == s2) // Вывод: true (оба указателя указывают на один экземпляр)
Closer (Доводчик)
Цель: Упрощение управления ресурсами, которые должны быть явно закрыты после использования (например, файлы, соединения).
Пример:
- Часто используется для автоматического закрытия ресурсов в конце блока кода.
Пример на Go:
type Closer interface {
Close() error
}
func UseResource(resource Closer) {
defer resource.Close()
// Работа с ресурсом
}
// Использование
file, err := os.Open("file.txt")
if err != nil {
log.Fatal(err)
}
UseResource(file)
Расскажите, какой паттерн использовали в продукте/своем коде?
Ниже — учебный пример однократной загрузки конфигурации. На собеседовании опишите собственную задачу и причины выбора паттерна; Singleton не обязателен для хранения конфигурации.
Решение: Использование паттерна Singleton для управления конфигурацией.
Код:
package config
import (
"sync"
"encoding/json"
"os"
)
type Config struct {
DatabaseURL string
Port int
}
var instance *Config
var once sync.Once
var loadErr error
func GetConfig() (*Config, error) {
once.Do(func() {
instance = &Config{}
file, err := os.Open("config.json")
if err != nil {
loadErr = err
return
}
defer file.Close()
loadErr = json.NewDecoder(file).Decode(instance)
})
return instance, loadErr
}
Использование:
package main
import (
"fmt"
"myapp/config"
)
func main() {
conf, err := config.GetConfig()
if err != nil {
fmt.Println("Cannot load config:", err)
return
}
fmt.Println("Database URL:", conf.DatabaseURL)
fmt.Println("Port:", conf.Port)
}
Преимущества:
-
Единая точка доступа: Конфигурация загружается один раз и используется во всех частях приложения.
-
sync.Once синхронизирует однократную загрузку, включая неуспешную попытку. Возвращаемую общую Config нельзя изменять конкурентно без дополнительной защиты; автоматического повтора загрузки здесь нет.