Паттерн Наблюдатель в Golang на котиках
Привет, Хабр! Сегодня будем разбирать паттерн Наблюдатель на примере наших любимых пушистиков — котиков. Ведь кто, как не коты, могут быть идеальными субъектами и наблюдателями в нашем коде?
Коротко про сам паттерн
Паттерн Наблюдатель позволяет субъекту уведомлять зависимые объекты (наблюдателей) о произошедших изменениях. Допустим, у вас есть кот, который каждый раз, когда видит лазерную указку, начинает бегать за ней. Лазерная указка — это субъект, а коты — наблюдатели. Как только что-то изменилось (появился лазер), все коты получают сигнал и начинают действовать.
Реализация на Golang
Реализуем этот паттерн на Go, используя котиков в качестве примера. Представим, что есть лазерная указка, и несколько котов, которые наблюдают за её движениями.
Определяем интерфейсы
Первым делом определим интерфейсы Subject
и Observer
.
// Subject представляет лазерную указку
type Subject interface {
RegisterObserver(o Observer) // Регистрация наблюдателя
RemoveObserver(o Observer) // Удаление наблюдателя
NotifyObservers() // Уведомление всех наблюдателей
}
// Observer представляет кота
type Observer interface {
Update(position string) // Метод обновления состояния
}
Интерфейсы — это как команды для котов: они знают, что делать, но не как именно.
Реализуем лазерную указку
Теперь создадим структуру LaserPointer
, которая будет субъектом.
type LaserPointer struct {
observers []Observer
position string
}
// RegisterObserver добавляет кота в список наблюдателей
func (lp *LaserPointer) RegisterObserver(o Observer) {
lp.observers = append(lp.observers, o)
}
// RemoveObserver убирает кота из списка наблюдателей
func (lp *LaserPointer) RemoveObserver(o Observer) {
for i, observer := range lp.observers {
if observer == o {
lp.observers = append(lp.observers[:i], lp.observers[i+1:]...)
break
}
}
}
// NotifyObservers уведомляет всех котов о новом положении лазера
func (lp *LaserPointer) NotifyObservers() {
for _, observer := range lp.observers {
observer.Update(lp.position)
}
}
// Move меняет позицию лазера и уведомляет котов
func (lp *LaserPointer) Move(newPosition string) {
lp.position = newPosition
lp.NotifyObservers()
}
Создаем котов-наблюдателей
Теперь реализуем структуру Cat
, которая будет наблюдателем.
func main() {
laser := &LaserPointer{}
cat1 := NewCat("Мурзик")
cat2 := NewCat("Барсик")
cat3 := NewCat("Симба")
laser.RegisterObserver(cat1)
laser.RegisterObserver(cat2)
laser.RegisterObserver(cat3)
positions := []string{"лево", "право", "вверх", "низ"}
for _, pos := range positions {
fmt.Printf("\nПеремещаем лазер в позицию: %s\n", pos)
laser.Move(pos)
time.Sleep(1 * time.Second) // Ждем секунду между перемещениями
}
// Убираем одного кота из гонки
laser.RemoveObserver(cat2)
fmt.Printf("\nБарсик устал и больше не будет гоняться за лазером.\n")
laser.Move("центр")
}
Каждый кот — уникален. Но в нашем примере все коты — как один.
Собираем всё вместе
Теперь объединим компоненты в главной функции.
Перемещаем лазер в позицию: лево
Кот Мурзик заметил лазер в позиции: лево и начинает гоняться за ним!
Кот Барсик заметил лазер в позиции: лево и начинает гоняться за ним!
Кот Симба заметил лазер в позиции: лево и начинает гоняться за ним!
Перемещаем лазер в позицию: право
Кот Мурзик заметил лазер в позиции: право и начинает гоняться за ним!
Кот Барсик заметил лазер в позиции: право и начинает гоняться за ним!
Кот Симба заметил лазер в позиции: право и начинает гоняться за ним!
...
Барсик устал и больше не будет гоняться за лазером.
Кот Мурзик заметил лазер в позиции: центр и начинает гоняться за ним!
Кот Симба заметил лазер в позиции: центр и начинает гоняться за ним!
Видите, как коты реагируют на каждое перемещение лазера? Это именно то, что делает паттерн Наблюдатель.
Безопасность
Реализуем хоть какую-то безопасность.
Потокобезопасность: если коты будут обрабатывать события параллельно, стоит использовать мьютексы или каналы для защиты общих ресурсов.
Обработка ошибок: например, проверка на
nil
наблюдателей при уведомлении.Логирование: вместо
fmt.Printf
использовать логгер для более гибкого управления выводом.
Пример с потокобезопасностью:
import (
"fmt"
"sync"
"time"
)
// LaserPointer с мьютексом для защиты списка наблюдателей
type LaserPointer struct {
observers []Observer
position string
mu sync.Mutex
}
func (lp *LaserPointer) RegisterObserver(o Observer) {
lp.mu.Lock()
defer lp.mu.Unlock()
lp.observers = append(lp.observers, o)
}
func (lp *LaserPointer) RemoveObserver(o Observer) {
lp.mu.Lock()
defer lp.mu.Unlock()
for i, observer := range lp.observers {
if observer == o {
lp.observers = append(lp.observers[:i], lp.observers[i+1:]...)
break
}
}
}
func (lp *LaserPointer) NotifyObservers() {
lp.mu.Lock()
defer lp.mu.Unlock()
for _, observer := range lp.observers {
// Запускаем обновление в отдельной горутине
go observer.Update(lp.position)
}
}
func (lp *LaserPointer) Move(newPosition string) {
lp.position = newPosition
lp.NotifyObservers()
}
Мьютекс защищает наш список котов от одновременных изменений. А горутины позволяют котикам гоняться за лазером параллельно.
Заключение
Теперь ваша очередь! Делитесь своими вариантами реализации и улучшениями в комментариях. Как вы используете этот паттерн в своем коде? Возможно, у вас есть свои фишки? Будет интересно обсудить!
Для Golang-разработчиков в ноябре пройдут два открытых урока:
12 ноября: «Использование каналов в Go на практике». Вспомним теории об устройстве каналов, рассмотрим примеры их применения и ошибки при их использовании. Записаться
21 ноября: «Пишем чистый Go‑код: паттерны». Особое внимание уделим singleflight, throttle, circuit breaker и другим популярным паттернам. Записаться