В программировании шаблоны (или паттерны) проектирования являются обобщёнными решениями для часто встречающихся проблем. Их использование способствует повышению уровня абстракции в проектах и облегчает поддержку и расширение кода [1].
Паттерн проектирования «наблюдатель» является ключевым элементом для реализации распределенных, межмодульных взаимодействий в программных системах. Основной целью его применения является создание механизма подписки, позволяющего объектам получать уведомления об изменениях состояния других объектов. Этот паттерн широко используется в различных областях разработки программного обеспечения, начиная от графических пользовательских интерфейсов и заканчивая системами реального времени [2].
Паттерн «наблюдатель» предполагает наличие двух типов объектов: «субъектов», состояние которых наблюдается, и «наблюдателей», которые подписываются на уведомления о изменениях состояния субъектов. Ключевым преимуществом паттерна является возможность динамического добавления и удаления наблюдателей, что обеспечивает высокую гибкость и масштабируемость системы.
Рассмотрим базовую реализацию паттерна «наблюдатель» на языке Go. Пример представлен на рисунках 1, 2 и 3.
Рис. 1. Пример создания паттерна “Наблюдатель”, создание интерфейсов
Рис. 2. Продолжение примера создания паттерна “Наблюдатель”, создание интерфейсов
Рис. 3. Продолжение примера создания паттерна “Наблюдатель”, создание субъекта и наблюдателей
В данном примере Subject является интерфейсом для субъектов, определяющим методы подписки (Attach), отписки (Detach) и уведомления наблюдателей (Notify). ConcreteSubject — конкретная реализация субъекта с состоянием state, изменение которого приводит к уведомлению наблюдателей. Observer определяет интерфейс для наблюдателей, а ConcreteObserver — его конкретная реализация, в которой определен метод Update, вызываемый при изменении состояния субъекта.
В Go паттерн «наблюдатель» имеет несколько важных особенностей:
- Потокобезопасность — в примере используется sync.RWMutex для обеспечения безопасной работы с наблюдателями в многопоточной среде. Это особенно важно для Go, где горутины активно используются.
- Интерфейсы — Go использует интерфейсы для определения контрактов между субъектами и наблюдателями, что обеспечивает гибкость и слабую связанность.
- Срезы для хранения наблюдателей — в отличие от списков в других языках, в Go используются срезы (slices) для хранения коллекции наблюдателей.
- Каналы как альтернатива — в Go также можно реализовать паттерн «наблюдатель» с использованием каналов (channels), что является более идиоматичным подходом для конкурентных приложений.
Паттерн «наблюдатель» представляет собой мощный инструмент в арсенале разработчика программного обеспечения, позволяющий эффективно управлять взаимодействиями между объектами в системе. Пример кода на языке Go демонстрирует базовую реализацию паттерна с учетом особенностей языка, включая потокобезопасность.
Однако, как и любой другой инструмент, паттерн «наблюдатель» имеет свои ограничения и подводные камни. Важно тщательно анализировать задачу и контекст её использования, чтобы избежать излишней сложности и неэффективности в работе системы. В Go особое внимание следует уделять потокобезопасности и рассмотреть возможность использования каналов для более идиоматичной реализации.
Литература:
- Донован А., Kernighan B. Язык программирования Go [Текст] / А. Донован, Б. Керниган. — М.: Вильямс, 2016. — 480 с.
- Цукалос М. Go: идиомы и паттерны проектирования [Текст] / М. Цукалос. — СПб.: Питер, 2020. — 544 с.

