Спостерігач, Observer — поведінковий шаблон проєктування. Також відомий як «підлеглі» (Dependents), «видавець-передплатник» (Publisher-Subscriber).
Призначення
Визначає залежність типу «один до багатьох» між об'єктами таким чином, що при зміні стану одного об'єкта всіх залежних від нього сповіщають про цю подію.
Переваги
- Він підтримує принцип вільного зв'язку між об'єктами, які взаємодіють один з одним
- Дозволяє ефективно передавати дані іншим об'єктам, без будь-яких змін у класах Subject або Observer
- Спостерігачі можуть бути додані / видалені в будь-який момент часу
Недоліки
- Інтерфейс Observer повинен бути впроваджений ConcreteObserver, який передбачає успадкування. Композиції для композиції немає, оскільки інтерфейс Observer може бути екземплятором.
- Якщо це неправильно реалізовано, спостерігач може додати складність і призвести до ненавмисних проблем із продуктивністю.
- У програмному застосуванні повідомлення іноді можуть бути невибагливими і призвести до умов перегонів або непослідовності.
Устрій
При реалізації шаблону «спостерігач» зазвичай використовуються такі класи:
- Subject — інтерфейс, що визначає методи для додавання, видалення та оповіщення спостерігачів.
- Observer — інтерфейс, за допомогою якого спостережуваний об'єкт оповіщає спостерігачів.
- ConcreteSubject — конкретний клас, який реалізує інтерфейс Subject.
- ConcreteObserver — конкретний клас, який реалізує інтерфейс Observer.
При зміні спостережуваного об'єкту, оповіщення спостерігачів може бути реалізоване за такими сценаріями:
- Спостережуваний об'єкт надсилає, кожному із зареєстрованих спостерігачів, всю потенційно релевантну інформацію (примусове розповсюдження).
- Спостережуваний об'єкт надсилає, кожному із зареєстрованих спостерігачів, лише повідомлення про те що інформація була змінена, а кожен із спостерігачів, за необхідності, самостійно здійснює запит необхідної інформації у спостережуваного об'єкта (розповсюдження за запитом).
Область застосування
Шаблон «спостерігач» застосовується в тих випадках, коли система володіє такими властивостями:
- існує, як мінімум, один об'єкт, що розсилає повідомлення
- є не менше одного одержувача повідомлень, причому їхня кількість і склад можуть змінюватися під час роботи програми.
Цей шаблон часто застосовують в ситуаціях, в яких відправника повідомлень не цікавить, що роблять одержувачі з наданою їм інформацією.
Простими словами
Об'єкт володіє важливими даними і на нього підписані спостерігачі. Кожен спостерігач має можливість обновити ці дані а інші спостерігачі повинні отримати про це сповіщення і обновитись в слід якщо це необхідно.
Спостерігач не повинен запитувати об'єкт з певною періодичністю, він завжди знає що його дані актуальні.
Зв'язок із іншими патернами
- Посередник створює двосторонній зв'язок, часто незмінний. Забирає залежності між компонентами системи. Компоненти стають залежними від посередника. Спостерігач створює односторонній зв'язок, який може мінятись під час виконання програми. Таким чином одні об'єкти залежать від інших.
Приклади
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); ?>
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"; } } }
#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; }
//файл 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 3.0 (який наслідував пакет mx.events у ActionScript 2.0).
BASIC
- , обговорення і реалізація в
C
- , у GLib — реалізація об'єктів і / (Callback) в C. (Ця бібліотека часто включена в інші мови програмування)
C++
- — бібліотека шаблонів
- 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++ implementation.
- libevent [ 23 травня 2010 у Wayback Machine.] — Multi-threaded Crossplatform Signal/Slot C++ Library
- , an extension of the C++ STL providing a signal/slot model
- The (Qt) C++ framework's
C#
- Exploring the Observer Design Pattern [ 7 березня 2010 у Wayback Machine.] — the (C#) and Visual Basic .NET implementation, using and the Event pattern
- , Applying the Observer Pattern in .NET Remoting (using C#)
- , a Delphi implementation
- 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.
- , a JavaScript core API based Signals and slots implementation — an observer concept different from — pretty lightweighted but still type-safety enforcing.
- 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
- , 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
- 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).
- implements custom events through the observer pattern
- , Implementation example of Observer or Publish/Subscribe using G.
Посилання
- Observer Pattern implementation in JDK 6 [ 6 серпня 2009 у Wayback Machine.]
- Observer Pattern in Java [ 30 березня 2010 у Wayback Machine.]
- Definition, C# example & UML diagram [ 23 липня 2010 у Wayback Machine.]
- J2EE Pattern Oriented Framework
- 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 с. — .
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет