Паттерн Наблюдатель в 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("центр") }

Каждый кот — уникален. Но в нашем примере все коты — как один.

Собираем всё вместе

Теперь объединим компоненты в главной функции.

Перемещаем лазер в позицию: лево Кот Мурзик заметил лазер в позиции: лево и начинает гоняться за ним! Кот Барсик заметил лазер в позиции: лево и начинает гоняться за ним! Кот Симба заметил лазер в позиции: лево и начинает гоняться за ним!  Перемещаем лазер в позицию: право Кот Мурзик заметил лазер в позиции: право и начинает гоняться за ним! Кот Барсик заметил лазер в позиции: право и начинает гоняться за ним! Кот Симба заметил лазер в позиции: право и начинает гоняться за ним!  ...  Барсик устал и больше не будет гоняться за лазером. Кот Мурзик заметил лазер в позиции: центр и начинает гоняться за ним! Кот Симба заметил лазер в позиции: центр и начинает гоняться за ним!

Видите, как коты реагируют на каждое перемещение лазера? Это именно то, что делает паттерн Наблюдатель.

Безопасность

Реализуем хоть какую-то безопасность.

  1. Потокобезопасность: если коты будут обрабатывать события параллельно, стоит использовать мьютексы или каналы для защиты общих ресурсов.

  2. Обработка ошибок: например, проверка на nil наблюдателей при уведомлении.

  3. Логирование: вместо 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *