Привет, Хабр! Сегодня будем разбирать паттерн Наблюдатель на примере наших любимых пушистиков — котиков. Ведь кто, как не коты, могут быть идеальными субъектами и наблюдателями в нашем коде?
Коротко про сам паттерн
Паттерн Наблюдатель позволяет субъекту уведомлять зависимые объекты (наблюдателей) о произошедших изменениях. Допустим, у вас есть кот, который каждый раз, когда видит лазерную указку, начинает бегать за ней. Лазерная указка — это субъект, а коты — наблюдатели. Как только что-то изменилось (появился лазер), все коты получают сигнал и начинают действовать.
Реализация на 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 и другим популярным паттернам. Записаться
ссылка на оригинал статьи https://habr.com/ru/articles/857330/
Добавить комментарий