Спостерігач (шаблон проєктування)
Спостерігач, Observer — поведінковий шаблон проєктування. Також відомий як «підлеглі» (Dependents), «видавець-передплатник» (Publisher-Subscriber).
Призначення
Визначає залежність типу «один до багатьох» між об'єктами таким чином, що при зміні стану одного об'єкта всіх залежних від нього сповіщають про цю подію.
Переваги
- Він підтримує принцип вільного зв'язку між об'єктами, які взаємодіють один з одним
- Дозволяє ефективно передавати дані іншим об'єктам, без будь-яких змін у класах Subject або Observer
- Спостерігачі можуть бути додані / видалені в будь-який момент часу
Недоліки
- Інтерфейс Observer повинен бути впроваджений ConcreteObserver, який передбачає успадкування. Композиції для композиції немає, оскільки інтерфейс Observer може бути екземплятором.
- Якщо це неправильно реалізовано, спостерігач може додати складність і призвести до ненавмисних проблем із продуктивністю.
- У програмному застосуванні повідомлення іноді можуть бути невибагливими і призвести до умов перегонів або непослідовності.
Устрій
При реалізації шаблону «спостерігач» зазвичай використовуються такі класи:
- Subject — інтерфейс, що визначає методи для додавання, видалення та оповіщення спостерігачів.
- Observer — інтерфейс, за допомогою якого спостережуваний об'єкт оповіщає спостерігачів.
- ConcreteSubject — конкретний клас, який реалізує інтерфейс Subject.
- ConcreteObserver — конкретний клас, який реалізує інтерфейс Observer.
При зміні спостережуваного об'єкту, оповіщення спостерігачів може бути реалізоване за такими сценаріями:
- Спостережуваний об'єкт надсилає, кожному із зареєстрованих спостерігачів, всю потенційно релевантну інформацію (примусове розповсюдження).
- Спостережуваний об'єкт надсилає, кожному із зареєстрованих спостерігачів, лише повідомлення про те що інформація була змінена, а кожен із спостерігачів, за необхідності, самостійно здійснює запит необхідної інформації у спостережуваного об'єкта (розповсюдження за запитом).
Область застосування
Шаблон «спостерігач» застосовується в тих випадках, коли система володіє такими властивостями:
- існує, як мінімум, один об'єкт, що розсилає повідомлення
- є не менше одного одержувача повідомлень, причому їхня кількість і склад можуть змінюватися під час роботи програми.
Цей шаблон часто застосовують в ситуаціях, в яких відправника повідомлень не цікавить, що роблять одержувачі з наданою їм інформацією.
Простими словами
Об'єкт володіє важливими даними і на нього підписані спостерігачі. Кожен спостерігач має можливість обновити ці дані а інші спостерігачі повинні отримати про це сповіщення і обновитись в слід якщо це необхідно.
Спостерігач не повинен запитувати об'єкт з певною періодичністю, він завжди знає що його дані актуальні.
Зв'язок із іншими патернами
- Посередник створює двосторонній зв'язок, часто незмінний. Забирає залежності між компонентами системи. Компоненти стають залежними від посередника. Спостерігач створює односторонній зв'язок, який може мінятись під час виконання програми. Таким чином одні об'єкти залежать від інших.
Приклади
Java
package example.pattern.observer;
import java.util.ArrayList;
import java.util.List;
public interface Subject {
void attach(Observer o);
void detach(Observer o);
void notifyObserver();
}
public interface Observer {
void update();
}
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
private int value;
public void setValue(int value) {
this.value = value;
notifyObserver();
}
public int getValue() {
return value;
}
@Override
public void attach(Observer o) {
observers.add(o);
}
@Override
public void detach(Observer o) {
observers.remove(o);
}
@Override
public void notifyObserver() {
for (Observer o : observers) {
o.update();
}
}
}
public class ConcreteObserver1 implements Observer {
private ConcreteSubject subject;
public ConcreteObserver1(ConcreteSubject subject) {
this.subject = subject;
}
@Override
public void update() {
System.out.println("Observer1: " + subject.getValue());
}
}
public class ConcreteObserver2 implements Observer {
private ConcreteSubject subject;
public ConcreteObserver2(ConcreteSubject subject) {
this.subject = subject;
}
@Override
public void update() {
String out = "";
for (int i = 0; i < subject.getValue(); i++) {
out += "*";
}
System.out.println("Observer2: " + out);
}
}
public class Program {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver1 observer1 = new ConcreteObserver1(subject);
ConcreteObserver2 observer2 = new ConcreteObserver2(subject);
subject.attach(observer1);
subject.attach(observer2);
subject.setValue(3);
subject.setValue(8);
}
}
PHP5
<?php
// Інтерфейс, за допомогою якого спостережуваний об'єкт сповіщає спостерігачів
interface Observer{
function notify($obj);
}
// Клас спостережуваного об'єкта
class ExchangeRate{
static private $instance = NULL;
private $observers = array();
private $exchange_rate;
private function ExchangeRate(){
}
static public function getInstance(){
if(self::$instance == NULL){
self::$instance = new ExchangeRate();
}
return self::$instance;
}
public function getExchangeRate(){
return $this->exchange_rate;
}
public function setExchangeRate($new_rate){
$this->exchange_rate = $new_rate;
$this->notifyObservers();
}
public function registerObserver($obj){
$this->observers[] = $obj;
}
function notifyObservers(){
foreach($this->observers as $obj){
$obj->notify($this);
}
}
}
// Клас спостерігача
class ProductItem implements Observer{
public function __construct(){
ExchangeRate::getInstance()->registerObserver($this);
}
public function notify($obj){
if($obj instanceof ExchangeRate) {
// Update exchange rate data
print "Received update!\n";
}
}
}
// Створення об'єктів спостерігачів
$product1 = new ProductItem();
$product2 = new ProductItem();
// Зміна стану спостережуваного об'єкта та автоматичне
// оповіщення про це спостерігачів через функцію notify()
ExchangeRate::getInstance()->setExchangeRate(4.5);
?>
C#
using System;
using System.Collections;
using System.Threading;
namespace Observer {
/// <summary>
/// Observer Pattern Judith Bishop Jan 2007
///
/// The Subject runs in a thread and changes its state
/// independently. At each change, it notifies its Observers.
/// </summary>
class Program {
static void Main(string[] args) {
Subject subject = new Subject();
Observer Observer = new Observer(subject,"Center","\t\t");
Observer observer2 = new Observer(subject,"Right","\t\t\t\t");
subject.Go();
// Wait for user
Console.Read();
}
}
class Simulator : IEnumerable {
string [] moves = {"5","3","1","6","7"};
public IEnumerator GetEnumerator() {
foreach( string element in moves )
yield return element;
}
}
class Subject {
public delegate void Callback (string s);
public event Callback Notify;
Simulator simulator = new Simulator( );
const int speed = 200;
public string SubjectState { get; set; }
public void Go() {
new Thread(new ThreadStart(Run)).Start( );
}
void Run () {
foreach (string s in simulator) {
Console.WriteLine("Subject: " + s);
SubjectState = s;
Notify(s);
Thread.Sleep(speed); // milliseconds
}
}
}
interface IObserver {
void Update(string state);
}
class Observer : IObserver {
string name;
Subject subject;
string state;
string gap;
public Observer(Subject subject, string name, string gap) {
this.subject = subject;
this.name = name;
this.gap = gap;
subject.Notify += Update;
}
public void Update(string subjectState) {
state = subjectState;
Console.WriteLine(gap + name + ": " + state);
}
}
}
namespace ObserverPattern.Interfaces
{
interface IEvent
{
}
interface IObserver<in TEvent> where TEvent : IEvent
{
void Handle(object sender, TEvent eventArgs);
}
interface ISubject<TEvent> where TEvent : IEvent
{
void Notify(TEvent raisedEvent);
void Add(IObserver<TEvent> observer);
void Remove(IObserver<TEvent> observer);
}
class UserRenamedEvent : IEvent
{
public string Name { get; }
public UserRenamedEvent(string name)
{
Name = name;
}
}
class User : ISubject<UserRenamedEvent>
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
this.Notify(new UserRenamedEvent(value));
}
}
private readonly ICollection<IObserver<UserRenamedEvent>> _userRenamedObservers = new List<IObserver<UserRenamedEvent>>();
public void Add(IObserver<UserRenamedEvent> observer)
{
_userRenamedObservers.Add(observer);
}
public void Remove(IObserver<UserRenamedEvent> observer)
{
_userRenamedObservers.Remove(observer);
}
public void Notify(UserRenamedEvent raisedEvent)
{
foreach (var observer in _userRenamedObservers)
{
observer.Handle(this, raisedEvent);
}
}
}
class ConsoleLogger : IObserver<UserRenamedEvent>
{
public void Handle(object sender, UserRenamedEvent eventArgs)
{
Console.WriteLine($"User has been renamed to [{eventArgs.Name}]");
}
}
class Program
{
static void Main(string[] args)
{
var logger = new ConsoleLogger();
var user = new User();
user.Add(logger);
user.Name = "John Doe";
}
}
}
namespace ObserverPattern.Events
{
class UserRenamedEvent : EventArgs
{
public string Name { get; }
public UserRenamedEvent(string name)
{
Name = name;
}
}
class User
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
this.OnUserRenamed(new UserRenamedEvent(value));
}
}
public event EventHandler<UserRenamedEvent> UserRenamedEvent;
// keep this protected to override event in derived class
protected void OnUserRenamed(UserRenamedEvent raisedEvent)
{
UserRenamedEvent?.Invoke(this, raisedEvent);
}
}
class ConsoleLogger
{
public void Handle(object sender, UserRenamedEvent eventArgs)
{
Console.WriteLine($"User has been renamed to [{eventArgs.Name}]");
}
}
class Program
{
static void Main(string[] args)
{
var logger = new ConsoleLogger();
var user = new User();
user.UserRenamedEvent += logger.Handle;
user.Name = "John Doe";
}
}
}
namespace ObserverPattern.EventService
{
interface IEvent
{
}
interface IEventHandler<in TEvent> where TEvent : IEvent
{
void Handle(object sender, TEvent raisedEvent);
}
interface IEventService
{
void Publish<TEvent>(object sender, TEvent raisedEvent)
where TEvent : IEvent;
void Subscribe<TEvent, THandler>()
where TEvent : IEvent
where THandler : IEventHandler<TEvent>;
}
class EventService : IEventService
{
private readonly IDictionary<Type, List<Type>> _eventHandlers = new Dictionary<Type, List<Type>>();
public void Publish<TEvent>(object sender, TEvent raisedEvent) where TEvent : IEvent
{
var eventType = typeof(TEvent);
if (!_eventHandlers.ContainsKey(eventType)) return;
foreach (var handlerType in _eventHandlers[eventType])
{
var handler = Activator.CreateInstance(handlerType) as IEventHandler<TEvent>;
handler.Handle(sender, raisedEvent);
}
}
public void Subscribe<TEvent, THandler>()
where TEvent : IEvent
where THandler : IEventHandler<TEvent>
{
var eventType = typeof(TEvent);
var handlerType = typeof(THandler);
if (!_eventHandlers.ContainsKey(eventType))
{
_eventHandlers.Add(eventType, new List<Type>());
}
if (_eventHandlers[eventType].Any(ht => ht == handlerType))
{
throw new ArgumentException($"Handler Type {handlerType.Name} already is registered for '{eventType.Name}'");
}
_eventHandlers[eventType].Add(handlerType);
}
public static EventService Instance { get; }
private EventService() { }
static EventService()
{
Instance = new EventService();
}
}
class UserRenamedEvent : IEvent
{
public string Name { get; }
public UserRenamedEvent(string name)
{
Name = name;
}
}
class User
{
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
EventService.Instance.Publish(this, new UserRenamedEvent(value));
}
}
}
class ConsoleLogger : IEventHandler<UserRenamedEvent>
{
public void Handle(object sender, UserRenamedEvent eventArgs)
{
Console.WriteLine($"User has been renamed to [{eventArgs.Name}]");
}
}
class Program
{
static void Main(string[] args)
{
EventService.Instance.Subscribe<UserRenamedEvent, ConsoleLogger>();
var user = new User();
user.Name = "John Doe";
}
}
}
C++
#include <iostream>
#include <string>
#include <map>
class SupervisedString;
class IObserver {
public:
virtual void handleEvent (const SupervisedString&) = 0;
};
class SupervisedString{ // Спостережний клас
std::string _str;
std::map<IObserver* const, IObserver* const> _observers;
void _Notify() {
for (auto &iter : _observers) {
iter.second->handleEvent (*this);
}
}
public:
void add (IObserver& ref) {
_observers.insert (item (&ref, &ref));
}
void remove (IObserver& ref) {
_observers.erase (&ref);
}
const std::string& get() const {
return _str;
}
void reset (std::string str) {
_str = str;
_Notify();
}
};
class Reflector: public IObserver{ // Надрукувати спостережуваний рядок у std::cout
public:
virtual void handleEvent (const SupervisedString& ref) {
std::cout<<ref.get()<<std::endl;
}
};
class Counter: public IObserver{ // Надрукувати довжину спостережуваного рядка в std::cout
virtual void handleEvent (const SupervisedString& ref) {
std::cout<<"length = "<<ref.get().length()<<std::endl;
}
};
int main() {
SupervisedString str;
Reflector refl;
Counter cnt;
str.add (refl);
str.reset ("Hello, World!");
std::cout<<std::endl;
str.remove (refl);
str.add (cnt);
str.reset ("World, Hello!");
std::cout<<std::endl;
return 0;
}
ActionScript
//файл IObserver.as
package
{
public interface IObserver
{
function notify(obj:Object):void;
}
}
//файл ExchangeRate.as
package
{
public class ExchangeRate
{
private static var _instance:ExchangeRate = null;
private var observers:Array = [];
private var _exchangeRate:Object;
public function ExchangeRate()
{
if (_instance == null) throw new Error('Model Singleton!');
}
public static function getInstance():ExchangeRate
{
if (_instance == null) {
_instance = new ExchangeRate();
}
return _instance;
}
public function get exchangeRate():Object
{
return _exchangeRate;
}
public function set exchangeRate(value:Object):void
{
_exchangeRate = value;
this.notifyObservers();
}
public function registerObserver(value:IObserver):void {
this.observers.push(value);
}
private function notifyObservers():void {
for each(var observer:IObserver in this.observers) {
observer.notify(this);
}
}
}
}
//файл ProductItem.as
package
{
public class ProductItem implements IObserver
{
public function ProductItem()
{
ExchangeRate.getInstance().registerObserver(this);
}
public function notify(value:Object):void {
if (value is ExchangeRate) {
var exchange:ExchangeRate = value as ExchangeRate;
trace(exchange.exchangeRate);
}
}
}
}
//файл Main.as
package
{
import flash.display.Sprite;
public class Main extends Sprite
{
public function Main():void
{
var item1:ProductItem = new ProductItem();
var item2:ProductItem = new ProductItem();
ExchangeRate.getInstance().exchangeRate = 3.5;
}
}
}
Реалізації
Шаблон Спостерігач реалізований в численних бібліотеках і системах, включаючи майже всі інструментарії графічних інтерфейсів користувача.
Деякі з найпомітніших реалізацій шаблону перелічені нижче:
ActionScript
- flash.events, пакет у ActionScript 3.0 (який наслідував пакет mx.events у ActionScript 2.0).
BASIC
- Using the Observer Pattern, обговорення і реалізація в REALbasic
C
- GObject, у GLib — реалізація об'єктів і сигналів/зворотних викликів (Callback) в C. (Ця бібліотека часто включена в інші мови програмування)
C++
- libsigc++ — бібліотека сигнальних шаблонів
- sigslot — C++ Signal/Slot Library
- Cpp::Events — Template-based C++ implementation that introduces separation of connection management interface of the event object from the invocation interface.
- XLObject[недоступне посилання з листопадаа 2019] — Template-based C++ signal/slot model patterned after Qt.
- Signals — A lightweight and non-intrusive C++ signal/slot model implementation.
- libevent — Multi-threaded Crossplatform Signal/Slot C++ Library
- Boost.Signals, an extension of the C++ STL providing a signal/slot model
- The Qt C++ framework's signal/slot model
C#
- Exploring the Observer Design Pattern — the C# and Visual Basic .NET implementation, using delegates and the Event pattern
.NET Remoting, Applying the Observer Pattern in .NET Remoting (using C#)
Delphi
- Delphi Observer Pattern, a Delphi implementation
Java
- The Java Swing library makes extensive use of the observer pattern for event management
- PerfectJPattern Open Source Project, Provides a context-free and type-safe implementation of the Observer Pattern in Java.
JavaScript
- EventDispatcher singleton, a JavaScript core API based Signals and slots implementation — an observer concept different from Publish/subscribe — pretty lightweighted but still type-safety enforcing.
Lisp
- Cells, a dataflow extension to Common Lisp that uses meta-programming to hide some of the details of Observer pattern implementation.
PHP
- Event_Dispatcher, a PHP implementation
- SPL, the Standard PHP Library
Python
- Py-notify, a Python implementation
- Observer Pattern using Weak References implementation by Michael Kent
- PyPubSub an in-application Pub/Sub library for Observer behavior
- NotificationFramework classes directly implementing Observer patterns
Ruby
- Observer, from the Ruby Standard Library. Also see Russ Olsen's coverage of this pattern in Ruby in Design Patterns in Ruby
Інше
- CSP — Observer Pattern using CSP-like Rendezvous (each actor is a process, communication is via rendezvous).
- YUI Event utility implements custom events through the observer pattern
- Publish/Subscribe with LabVIEW, Implementation example of Observer or Publish/Subscribe using G.
Посилання
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.