Спостерігач (шаблон проєктування)
Спостерігач, 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 [Архівовано 14 березня 2010 у Wayback Machine.] — C++ Signal/Slot Library
- Cpp::Events [Архівовано 24 липня 2010 у Wayback Machine.] — Template-based C++ implementation that introduces separation of connection management interface of the event object from the invocation interface.
- XLObject [Архівовано 8 серпня 2010 у Wayback Machine.] — Template-based C++ signal/slot model patterned after Qt.
- Signals [Архівовано 9 липня 2010 у Wayback Machine.] — A lightweight and non-intrusive C++ signal/slot model implementation.
- libevent [Архівовано 23 травня 2010 у Wayback Machine.] — 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 [Архівовано 7 березня 2010 у Wayback Machine.] — 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 [Архівовано 29 липня 2010 у Wayback Machine.], 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 [Архівовано 17 липня 2011 у Wayback Machine.], a dataflow extension to Common Lisp that uses meta-programming to hide some of the details of Observer pattern implementation.
PHP[ред. | ред. код]
- Event_Dispatcher [Архівовано 28 березня 2010 у Wayback Machine.], a PHP implementation
- SPL [Архівовано 12 квітня 2011 у Wayback Machine.], the Standard PHP Library
Python[ред. | ред. код]
- Py-notify, a Python implementation
- Observer Pattern using Weak References [Архівовано 16 травня 2010 у Wayback Machine.] implementation by Michael Kent
- PyPubSub [Архівовано 16 травня 2010 у Wayback Machine.] an in-application Pub/Sub library for Observer behavior
- NotificationFramework [Архівовано 6 липня 2010 у Wayback Machine.] classes directly implementing Observer patterns
Ruby[ред. | ред. код]
- Observer [Архівовано 29 квітня 2010 у Wayback Machine.], from the Ruby Standard Library. Also see Russ Olsen's coverage of this pattern in Ruby in Design Patterns in Ruby [Архівовано 11 липня 2010 у Wayback Machine.]
Інше[ред. | ред. код]
- CSP [Архівовано 13 червня 2010 у Wayback Machine.] — 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.
Посилання[ред. | ред. код]
- Observer Pattern implementation in JDK 6 [Архівовано 6 серпня 2009 у Wayback Machine.]
- A sample implementation in .NET
- Observer Pattern in Java [Архівовано 30 березня 2010 у Wayback Machine.]
- Definition, C# example & UML diagram [Архівовано 23 липня 2010 у Wayback Machine.]
- Jt J2EE Pattern Oriented Framework
- Subject Observer example in C++
- Observer Pattern recipe in Python [Архівовано 16 квітня 2010 у Wayback Machine.]
Джерела[ред. | ред. код]
- Design Patterns: Elements of Reusable Object-Oriented Software [Архівовано 9 листопада 2012 у Wayback Machine.]
Література[ред. | ред. код]
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.