Ответ
В Go, благодаря его особенностям (интерфейсы, функции первого класса, горутины и каналы), классические паттерны GoF часто реализуются иначе или заменяются более идиоматичными подходами. Однако многие концепции остаются актуальными.
Классические паттерны
-
Фабричный метод (Factory Method)
- Описание: Создание объектов через специальную функцию, а не напрямую через конструктор. Это позволяет скрыть сложную логику инициализации.
- Пример:
// NewFileLogger и NewConsoleLogger — это фабричные функции func NewLogger(logType string) (Logger, error) { switch logType { case "file": return NewFileLogger("/var/log/app.log"), nil case "console": return NewConsoleLogger(), nil default: return nil, fmt.Errorf("unknown logger type: %s", logType) } }
-
Одиночка (Singleton)
- Описание: Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В Go идиоматично реализуется с помощью
sync.Once. - Пример:
type singleton struct{}
var ( instance *singleton once sync.Once )
func GetInstance() *singleton { once.Do(func() { instance = &singleton{} }) return instance }
- Описание: Гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к нему. В Go идиоматично реализуется с помощью
-
Декоратор (Decorator)
- Описание: Динамически добавляет объекту новую функциональность, «оборачивая» его. В Go легко реализуется через встраивание (embedding) интерфейсов.
- Пример: Логгирование запросов к HTTP-хендлеру.
func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("Request: %s %s", r.Method, r.URL.Path) next.ServeHTTP(w, r) }) }
// Использование: http.Handle("/", LoggingMiddleware(myHandler))
-
Стратегия (Strategy)
- Описание: Определяет семейство схожих алгоритмов и помещает каждый из них в собственный класс, после чего алгоритмы можно взаимозаменять. В Go это достигается через интерфейсы.
- Пример:
type Sorter interface { Sort(data []int) }
type BubbleSort struct{} func (b BubbleSort) Sort(data []int) { / ... / }
type QuickSort struct{} func (q QuickSort) Sort(data []int) { / ... / }
// Контекст, который использует стратегию type Context struct { sorter Sorter }
func (c *Context) DoSort(data []int) { c.sorter.Sort(data) }
Go-идиоматичные паттерны
-
Наблюдатель (Observer) через каналы
- Описание: Вместо сложных интерфейсов и списков подписчиков, в Go для рассылки событий используются каналы. Это проще и безопаснее с точки зрения конкурентности.
- Пример:
// Издатель рассылает события в канал func publisher(ch chan<- string) { for i := 0; i < 5; i++ { ch <- fmt.Sprintf("Event %d", i) time.Sleep(time.Second) } close(ch) }
// Подписчик читает из канала func subscriber(ch <-chan string) { for event := range ch { log.Println("Received:", event) } }
-
Функциональные опции (Functional Options)
- Описание: Паттерн для создания сложных объектов с множеством необязательных параметров. Он более гибок, чем использование структуры с настройками.
- Пример:
type Server struct { Addr string Port int Timeout time.Duration }
type Option func(*Server)
func WithAddr(addr string) Option { return func(s *Server) { s.Addr = addr } }
func WithPort(port int) Option { return func(s *Server) { s.Port = port } }
func NewServer(opts ...Option) Server { // Значения по умолчанию srv := &Server{Addr: "localhost", Port: 8080, Timeout: time.Second 30} // Применяем опции for _, opt := range opts { opt(srv) } return srv }
// Использование: server := NewServer(WithPort(9000), WithAddr("0.0.0.0"))
Ответ 18+ 🔞
Смотри, вот тебе про паттерны в Go, но без этой академической зауми. Тут всё как в жизни — либо берёшь классику и перекраиваешь под местные нравы, либо придумываешь своё, потому что старые подходы тут просто не влезают, блядь.
Классика, но в штанах из джинсы
-
Фабричный метод (Factory Method)
- Суть: Не пались с конструкторами напрямую, а спрячь эту хуйню в функцию. Чтобы потом не орать "а почему у меня тут nil, сука?", когда что-то пошло не так.
- Как выглядит:
// NewFileLogger и NewConsoleLogger — это и есть наши фабрики, ёпта func NewLogger(logType string) (Logger, error) { switch logType { case "file": return NewFileLogger("/var/log/app.log"), nil case "console": return NewConsoleLogger(), nil default: return nil, fmt.Errorf("unknown logger type: %s", logType) // Чёт не то ввели — получай ошибку, а не панику } }
-
Одиночка (Singleton)
- Суть: Чтобы этот ваш объект был один, как хуй в бане, на весь проект. В Go для этого есть
sync.Once— штука, которая делает что-то один раз и потом уже ни в какую. - Смотри сюда:
type singleton struct{}
var ( instance *singleton once sync.Once // Главный по тарелочкам, блядь )
func GetInstance() *singleton { once.Do(func() { // Эта хуйня сработает ровно один раз, даже если с десяти горутин дернуть instance = &singleton{} }) return instance }
- Суть: Чтобы этот ваш объект был один, как хуй в бане, на весь проект. В Go для этого есть
-
Декоратор (Decorator)
- Суть: Надо добавить функциональность? Просто оберни, как портянкой! В Go это часто мидлвари для HTTP.
- Пример: Хочешь логировать запросы? На, заверни хендлер!
func LoggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { log.Printf("Запрос пришёл: %s %s", r.Method, r.URL.Path) // Сначала залогировали next.ServeHTTP(w, r) // А потом уже отдали дальше, по назначению }) }
// Используем: http.Handle("/", LoggingMiddleware(myHandler)) // И всё, хендлер теперь в логирующей обёртке
-
Стратегия (Strategy)
- Суть: Много алгоритмов, а выбрать нужно один. Засовываем каждый в свою структуру, но заставляем реализовывать один интерфейс. Красота!
- Вот:
type Sorter interface { Sort(data []int) // Контракт, блядь. Хочешь быть сортировщиком — сортируй. }
type BubbleSort struct{} // Пузырёк, медленный, но свой func (b BubbleSort) Sort(data []int) { / ... / }
type QuickSort struct{} // Быстрая, хитрая жопа func (q QuickSort) Sort(data []int) { / ... / }
// А это наш главный, который будет решать, кого сегодня вызывать type Context struct { sorter Sorter }
func (c *Context) DoSort(data []int) { c.sorter.Sort(data) // А кто там внутри — ему похуй, главное чтобы метод был }
А это уже наше, родное, Go-шное
-
Наблюдатель (Observer) через каналы
- Суть: Забудь про эти ваши списки подписчиков и интерфейсы с
Update(). В Го события — это каналы. Просто, как три копейки, и конкурентно безопасно, ёпта. - Смотри, как просто:
// Издатель, который шлёт события в канал func publisher(ch chan<- string) { for i := 0; i < 5; i++ { ch <- fmt.Sprintf("Событие %d", i) // Отправил time.Sleep(time.Second) } close(ch) // Всё, больше писем не будет, закрываем лавочку }
// Подписчик, который жрёт события из канала func subscriber(ch <-chan string) { for event := range ch { // Читает, пока канал не закроют log.Println("Получил:", event) } }
- Суть: Забудь про эти ваши списки подписчиков и интерфейсы с
-
Функциональные опции (Functional Options)
- Суть: Это пиздец какой удобный паттерн, когда у тебя объект с кучей настроек, и половина из них необязательные. Вместо того чтобы городить конструктор с двадцатью параметрами или передавать
Config-структуру, ты просто накидываешь опций. Красота, ядрёна вошь! - Вот как жизнь налаживается:
type Server struct { Addr string Port int Timeout time.Duration }
// Опция — это просто функция, которая тыкает в сервер type Option func(*Server)
func WithAddr(addr string) Option { return func(s *Server) { s.Addr = addr } }
func WithPort(port int) Option { return func(s *Server) { s.Port = port } }
func NewServer(opts ...Option) Server { // Сначала дефолтные настройки srv := &Server{Addr: "localhost", Port: 8080, Timeout: time.Second 30} // А теперь прогоняем все переданные опции for _, opt := range opts { opt(srv) // Каждая опция меняет что-то в сервере } return srv }
// Использование — просто песня: server := NewServer(WithPort(9000), WithAddr("0.0.0.0")) // Хочу на другом порту и слушать все интерфейсы!
- Суть: Это пиздец какой удобный паттерн, когда у тебя объект с кучей настроек, и половина из них необязательные. Вместо того чтобы городить конструктор с двадцатью параметрами или передавать
Вот и вся магия. В Go часто проще и надёжнее использовать его родные механизмы — горутины, каналы, интерфейсы и функции первого класса, чем пытаться впихнуть классические ООП-паттерны в лоб. Получается идиоматичнее и без лишнего геморроя.