Наблюдатель (шаблон проектирования)
Из Википедии, бесплатной энциклопедии
Наблюдатель | |
---|---|
Observer | |
Тип | поведенческий |
Назначение |
|
Описан в Design Patterns | Да |
Наблюдатель (англ. Observer) — поведенческий шаблон проектирования. Также известен как «подчинённые» (англ. Dependents). Реализует у класса механизм, который позволяет объекту этого класса получать оповещения об изменении состояния других объектов и тем самым наблюдать за ними[2].
Классы, на события которых другие классы подписываются, называются субъектами (Subjects), а подписывающиеся классы называются наблюдателями (англ. Observers)[3].
Похожие шаблоны: «издатель — подписчик», «посредник», «одиночка».
Назначение[править | править код]
Определяет зависимость типа один ко многим между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом событии.
Реализация[править | править код]
При реализации шаблона «наблюдатель» обычно используются следующие классы:
- Observable — интерфейс, определяющий методы для добавления, удаления и оповещения наблюдателей;
- Observer — интерфейс, с помощью которого наблюдатель получает оповещение;
- ConcreteObservable — конкретный класс, который реализует интерфейс Observable;
- ConcreteObserver — конкретный класс, который реализует интерфейс Observer.
Область применения[править | править код]
Шаблон «наблюдатель» применяется в тех случаях, когда система обладает следующими свойствами:
- существует как минимум один объект, рассылающий сообщения;
- имеется не менее одного получателя сообщений, причём их количество и состав могут изменяться во время работы приложения;
- позволяет избежать сильного зацепления взаимодействующих классов.
Данный шаблон часто применяют в ситуациях, в которых отправителя сообщений не интересует, что делают получатели с предоставленной им информацией.
Примеры[править | править код]
PHP5 (SPL)[править | править код]
/** * В PHP осуществляется встроенная поддержка этого шаблона через входящее в поставку * расширение SPL (Standard PHP Library): * SplObserver - интерфейс для Observer (наблюдателя), * SplSubject - интерфейс Observable (наблюдаемого), * SplObjectStorage - вспомогательный класс (обеспечивает улучшенное сохранение и удаление * объектов, в частности, реализованы методы attach() и detach()). */ class Observable implements SplSubject { private $storage; function __construct() { $this->storage = new SplObjectStorage(); } function attach(SplObserver $observer) { $this->storage->attach($observer); } function detach(SplObserver $observer) { $this->storage->detach($observer); } function notify() { foreach($this->storage as $obj) { $obj->update($this); } } } class ConcreteObserver implements SplObserver { private $observable; private $index; function __construct(Observable $observable) { static $sindex=0; $this->index=$sindex++; $this->observable = $observable; $observable->attach($this); } function update(SplSubject $subject) { if($subject === $this->observable) { echo "Send notify to ConcreteObserver [$this->index]\n"; } } } $observable = new Observable(); new ConcreteObserver($observable); new ConcreteObserver($observable); new ConcreteObserver($observable); $observable->notify();
PHP5[править | править код]
interface Observer { function notify($obj); } class ExchangeRate { static private $instance = NULL; private $observers = array(); private $exchange_rate; private function __construct() {} private function __clone() {} 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(Observer $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(); ExchangeRate::getInstance()->setExchangeRate(4.5);
C#[править | править код]
using System; using System.Collections; using System.Collections.Generic; using System.Threading; namespace Observer { /// <summary> /// Observer Pattern Judith Bishop Jan 2007 /// Updated by Kobel' Bohdan 2013 /// /// 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; } } interface ISubject { void AddObserver(IObserver observer); void RemoveObserver(IObserver observer); void NotifyObservers(string s); } class Subject : ISubject { public string SubjectState { get; set; } public List<IObserver> Observers { get; private set; } private Simulator simulator; private const int speed = 200; public Subject() { Observers = new List<IObserver>(); simulator = new Simulator(); } public void AddObserver(IObserver observer) { Observers.Add(observer); } public void RemoveObserver(IObserver observer) { Observers.Remove(observer); } public void NotifyObservers(string s) { foreach (var observer in Observers) { observer.Update(s); } } public void Go() { new Thread(new ThreadStart(Run)).Start( ); } void Run () { foreach (string s in simulator) { Console.WriteLine("Subject: " + s); SubjectState = s; NotifyObservers(s); Thread.Sleep(speed); // milliseconds } } } interface IObserver { void Update(string state); } class Observer : IObserver { string name; ISubject subject; string state; string gap; public Observer(ISubject subject, string name, string gap) { this.subject = subject; this.name = name; this.gap = gap; subject.AddObserver(this); } public void Update(string subjectState) { state = subjectState; Console.WriteLine(gap + name + ": " + state); } } }
Java[править | править код]
// В примере описывается получение данных от метеорологической станции (класс WeatherData, рассылатель событий) и //использование их для вывода на экран (класс CurrentConditionsDisplay, слушатель событий). //Слушатель регистрируется у наблюдателя с помощью метода registerObserver (при этом слушатель заносится в список observers). //Регистрация происходит в момент создания объекта currentDisplay, т.к. метод registerObserver применяется в конструкторе. //При изменении погодных данных вызывается метод notifyObservers, который в свою очередь вызывает метод update //у всех слушателей, передавая им обновлённые данные. import java.util.LinkedList; import java.util.List; public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); Observer currentDisplay = new CurrentConditionsDisplay (weatherData); weatherData.setMeasurements(29f, 65f, 745); weatherData.setMeasurements(39f, 70f, 760); weatherData.setMeasurements(42f, 72f, 763); } } interface Observer { void update (float temperature, float humidity, int pressure); } interface Observable { void registerObserver(Observer o); void removeObserver(Observer o); void notifyObservers(); } class WeatherData implements Observable { private List<Observer> observers; private float temperature; private float humidity; private int pressure; public WeatherData() { observers = new LinkedList<>(); } @Override public void registerObserver(Observer o) { observers.add(o); } @Override public void removeObserver(Observer o) { observers.remove(o); } @Override public void notifyObservers() { for (Observer observer : observers) observer.update(temperature, humidity, pressure); } public void setMeasurements(float temperature, float humidity, int pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; notifyObservers(); } } class CurrentConditionsDisplay implements Observer { private float temperature; private float humidity; private int pressure; public CurrentConditionsDisplay(Observable weatherData) { weatherData.registerObserver(this); } @Override public void update(float temperature, float humidity, int pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; display(); } public void display() { System.out.printf("Сейчас значения:%.1f градусов цельсия и %.1f %% влажности. Давление %d мм рт. ст.\n", temperature, humidity, pressure); } }
C++[править | править код]
#include <iostream> #include <string> #include <list> using namespace std; class SupervisedString; class IObserver { public: virtual void handleEvent(const SupervisedString&) = 0; }; class SupervisedString // Observable class { string _str; list<IObserver*> _observers; void _Notify() { for(auto& observer: _observers) { observer->handleEvent(*this); } } public: void add(IObserver& ref) { _observers.push_back(&ref); } void remove(IObserver& ref) { _observers.remove(&ref); } const string& get() const { return _str; } void reset(string str) { _str = str; _Notify(); } }; class Reflector: public IObserver // Prints the observed string into cout { public: virtual void handleEvent(const SupervisedString& ref) { cout << ref.get() << endl; } }; class Counter: public IObserver // Prints the length of observed string into cout { public: virtual void handleEvent(const SupervisedString& ref) { cout << "length = " << ref.get().length() << endl; } }; int main() { SupervisedString str; Reflector refl; Counter cnt; str.add(refl); str.reset("Hello, World!"); cout << endl; str.remove(refl); str.add(cnt); str.reset("World, Hello!"); cout << 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; } } }
VB.NET[править | править код]
Imports System.Collections Imports 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 Shared Sub Main() Dim subject As New Subject() Dim Observer As New Observer(subject, "Center", vbTab & vbTab) Dim observer2 As New Observer(subject, "Right", vbTab & vbTab & vbTab & vbTab) subject.Go() ' Wait for user Console.Read() End Sub End Class Class Simulator Implements IEnumerable Private moves As String() = {"5", "3", "1", "6", "7"} Public Function GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return moves.GetEnumerator ' // Yield End Function End Class Class Subject Public Delegate Sub Callback(ByVal s As String) Public Event Notify As Callback Private simulator As New Simulator() Private m_SubjectState As String Private Const speed As Integer = 200 Public Property SubjectState() As String Get Return m_SubjectState End Get Set(ByVal value As String) m_SubjectState = value End Set End Property Public Sub Go() Call (New Thread(New ThreadStart(AddressOf Run))).Start() End Sub Private Sub Run() For Each s As String In simulator Console.WriteLine("Subject: " & s) SubjectState = s RaiseEvent Notify(s) ' milliseconds Thread.Sleep(speed) Next End Sub End Class Interface IObserver Sub Update(ByVal state As String) End Interface Class Observer Implements IObserver Private name As String Private subject As Subject Private state As String Private gap As String Public Sub New(ByVal subject As Subject, ByVal name As String, ByVal gap As String) Me.subject = subject Me.name = name Me.gap = gap AddHandler subject.Notify, AddressOf Update End Sub Public Sub Update(ByVal subjectState As String) Implements IObserver.Update state = subjectState Console.WriteLine(gap & name & ": " & state) End Sub End Class End Namespace
Python[править | править код]
from abc import ABCMeta, abstractmethod class Observer(metaclass=ABCMeta): """ Абстрактный наблюдатель """ @abstractmethod def update(self, message: str) -> None: """ Получение нового сообщения """ pass class Observable(metaclass=ABCMeta): """ Абстрактный наблюдаемый """ def __init__(self) -> None: """ Constructor. """ self.observers = [] # инициализация списка наблюдателей def register(self, observer: Observer) -> None: """ Регистрация нового наблюдателя на подписку """ self.observers.append(observer) def notify_observers(self, message: str) -> None: """ Передача сообщения всем наблюдателям, подписанным на события данного объекта наблюдаемого класса """ for observer in self.observers: observer.update(message) class Newspaper(Observable): """ Газета, за новостями в которой следят тысячи людей """ def add_news(self, news: str) -> None: """ Выпуск очередной новости """ self.notify_observers(news) class Citizen(Observer): """ Обычный гражданин, который любит читнуть с утра любимую газетку """ def __init__(self, name: str) -> None: """ Constructor. :param name: имя гражданина, чтоб не спутать его с кем-то другим """ self.name = name def update(self, message: str) -> None: """ Получение очередной новости """ print(f'{self.name} узнал следующее: {message}') if __name__ == '__main__': newspaper = Newspaper() # создаем небольшую газету newspaper.register(Citizen('Иван')) # добавляем двух человек, которые newspaper.register(Citizen('Василий')) # ... ее регулярно выписывают # ... и вбрасываем очередную газетную утку newspaper.add_news('Наблюдатель - поведенческий шаблон проектирования') ''' Иван узнал следующее: Наблюдатель - поведенческий шаблон проектирования Василий узнал следующее: Наблюдатель - поведенческий шаблон проектирования '''
Object Pascal[править | править код]
program observer; /// Observer Pattern Judith Bishop Jan 2007 /// Ported to Pascal by Dmitry Boyarintsev, May 2018 /// /// The Subject runs in a thread and changes its state /// independently. At each change, it notifies its Observers. {$ifdef fpc}{$mode delphi}{$H+}{$endif} uses SysUtils, Classes; type TBaseObserver = class(TObject) procedure Update(const astate: string); virtual; abstract; end; TBaseSubject = class(TObject) procedure AddObserver(aobserver: TBaseObserver); virtual; abstract; procedure RemoveObserver(aobserver: TBaseObserver); virtual; abstract; procedure NotifyObservers(const s: string); virtual; abstract; end; type { TSubject } TSubject = class(TBaseSubject) private fObservers : TList; fSimulator : TStringList; speed : Integer; protected procedure Run; public constructor Create; destructor Destroy; override; procedure AddObserver(aobserver: TBaseObserver); override; procedure RemoveObserver(aobserver: TBaseObserver); override; procedure NotifyObservers(const astate: string); override; procedure Go; end; TObserver = class (TBaseObserver) private fname : string; fsubject : TBaseSubject; fstate : string; fgap : string; public constructor Create(asubject: TBaseSubject; const aname, agap: string); procedure Update(const astate: string); override; end; { TSubject } procedure TSubject.Run; var i : integer; s : string; begin for i:=0 to fSimulator.Count-1 do begin s := fSimulator[i]; Writeln('Subject: ',s); NotifyObservers(s); Sleep(speed); // milliseconds end; end; constructor TSubject.Create; begin inherited Create; fObservers := TList.Create; speed := 200; fSimulator := TStringList.Create; fSimulator.AddStrings(['5','3','1','6','7']); end; destructor TSubject.Destroy; begin fObservers.Free; fSimulator.Free; end; procedure TSubject.AddObserver(aobserver: TBaseObserver); begin fObservers.Add(aobserver); end; procedure TSubject.RemoveObserver(aobserver: TBaseObserver); begin fObservers.Remove(aobserver); end; procedure TSubject.NotifyObservers(const astate: string); var i : integer; begin for i:=0 to fObservers.Count-1 do TBaseObserver(fObservers[i]).Update(astate); end; type { TMethodThread } TMethodThread = class(TThread) protected fMethod: TThreadMethod; procedure Execute; override; public constructor Create(AMethod: TThreadMethod); end; { TMethodThread } constructor TMethodThread.Create(AMethod: TThreadMethod); begin fMethod := AMethod; FreeOnTerminate := True; inherited Create(false); end; procedure TMethodThread.Execute; begin if Assigned(fMethod) then fMethod(); end; procedure TSubject.Go; begin TMethodThread.Create(Self.Run); end; constructor TObserver.Create(asubject: TBaseSubject; const aname, agap: string); begin inherited Create; fsubject := asubject; fname := aname; fgap := agap; if Assigned(fsubject) then fsubject.AddObserver(self); end; procedure TObserver.Update(const astate: string); begin fstate := astate; writeln(fgap, fname, ': ', astate); end; /// Main Program var subject : TSubject; observer : TObserver; observer2 : TObserver; begin subject := TSubject.Create; observer := TObserver.Create(subject, 'Center', #9#9); observer2 := TObserver.Create(subject,'Right',#9#9#9#9); try subject.Go(); // Wait for user readln; finally observer.Free; observer2.Free; subject.Free; end; end.
Ruby[править | править код]
module Observable def initialize @observers = [] end def add_observer(observer) @observers << observer unless @observers.include?(observer) end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each {|x| x.update(self)} end end class Employee include Observable attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) super() @name = name @title = title @salary = salary end end class BaseObserver def update raise 'Must be implement "update" function' end end class Payroll < BaseObserver def update(employee ) p("Cut a new check for #{employee.name}!") p("His salary is now #{employee.salary}!") end end class TaxMan < BaseObserver def update(employee) p("Send #{employee.name} a new tax bill!") end end mike = Employee.new('Mike', 'project manger', 25000) mike.add_observer(Payroll.new) mike.add_observer(TaxMan.new) mike.salary = 35000 mike.title = 'senior project manger' mike.notify_observers =begin Результат "Cut a new check for Mike!" "His salary is now 35000!" "Send Mike a new tax bill!" =end
Rust[править | править код]
/// В примере описывается получение данных от метеорологической станции (структура WeatherData, рассылатель событий) и /// использование их для вывода на экран (структура CurrentConditionsDisplay, слушатель событий). /// Слушатель регистрируется у наблюдателя с помощью метода register_observer, который принимает замыкание и заносит его /// в список observers. При изменении погодных данных вызывается метод notify_observers, который выполняет замыкания /// всех слушателей, передавая им обновлённые данные. use std::rc::Rc; use std::cell::RefCell; type ObserverFn = Box<dyn Fn(f32, f32, i32)>; trait Observable { fn register_observer(&mut self, o: ObserverFn) -> usize; fn remove_observer(&mut self, idx: usize); fn notify_observers(&mut self); } #[derive(Default)] struct WeatherData { observers: Vec<ObserverFn>, temperature: f32, humidity: f32, pressure: i32, } impl WeatherData { fn set_measurements(&mut self, temperature: f32, humidity: f32, pressure: i32) { self.temperature = temperature; self.humidity = humidity; self.pressure = pressure; self.notify_observers(); } } impl Observable for WeatherData { fn register_observer(&mut self, o: ObserverFn) -> usize { self.observers.push(o); self.observers.len() - 1 } fn remove_observer(&mut self, idx: usize) { self.observers.remove(idx); } fn notify_observers(&mut self) { for observer in self.observers.iter() { (*observer)(self.temperature, self.humidity, self.pressure); } } } #[derive(Default)] struct CurrentConditionsDisplay { temperature: f32, humidity: f32, pressure: i32, } impl CurrentConditionsDisplay { fn display(&self) { println!("Сейчас значения: {:.1} градусов цельсия и {:.1} % влажности. Давление {} мм рт. ст.", self.temperature, self.humidity, self.pressure); } fn update(&mut self, temperature: f32, humidity: f32, pressure: i32) { self.temperature = temperature; self.humidity = humidity; self.pressure = pressure; self.display(); } } fn main() { let mut weather_data = WeatherData::default(); let current_display = Rc::new(RefCell::new(CurrentConditionsDisplay::default())); let observer = current_display.clone(); weather_data.register_observer(Box::new(move |t, h, p| observer.borrow_mut().update(t, h, p))); weather_data.set_measurements(29.0, 65.0, 745); weather_data.set_measurements(39.0, 70.0, 760); weather_data.set_measurements(42.0, 72.0, 763); }
# Пример полностью идентичный тому, который выше на Python Observer := Object clone Observable := List clone do( register := getSlot("push") notify := method(message, self foreach(observer, observer update(message))) ) Newspaper := Observable clone do( addNews := method(news, notify(news))) Citizen := Observer clone do( create := method(name, self clone lexicalDo(name := name)) update := method(message, writeln( name .. " узнал следующее: " .. message)) ) newspaper := Newspaper clone newspaper do( register(Citizen create("Иван")) register(Citizen create("Василий")) addNews("Наблюдатель - поведенческий шаблон проектирования") ) #>>>> Иван узнал следующее: Наблюдатель - поведенческий шаблон проектирования #>>>> Василий узнал следующее: Наблюдатель - поведенческий шаблон проектирования
JavaScript ES6[править | править код]
class Observable { constructor() { this.listeners = {}; } // Подписаться. on(e, callback) { if (this.listeners[e] == undefined) { this.listeners[e] = {}; this.listeners[e].eventProperty = {}; this.listeners[e].eventProperty.isOnOnce = false; this.listeners[e].data = []; } this.listeners[e].data.push(callback); } // Подписаться единожды. onOnce(e, callback) { this.on(e, callback); this.listeners[e].eventProperty.isOnOnce = true; } // Отписаться. off(e, callback) { this.listeners[e].data = this.listeners[e].data. filter(function (listener) { return listener !== callback; }); } // Разослать сообщение подписчикам. emit(e, data) { if (this.listeners[e] == undefined || this.listeners[e].data == undefined) { return; } let itObj = this; this.listeners[e].data.forEach(listener => { if(itObj.listeners[e].eventProperty.isOnOnce) { itObj.off(e, itObj.listeners[e].data[0]); } listener(data); }); } }
Дополнительная информация[править | править код]
В платформе .NET Framework 4.0 шаблон разработки наблюдателя применяется путём реализации универсальных интерфейсов System.IObservable<T>
и System.IObserver<T>
[2].
Литература[править | править код]
- Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес. Приемы объектно-ориентированного проектирования. Паттерны проектирования = Design Patterns. Elements of Reusable Object-Oriented Software. — СПб.: Питер, 2009. — 366 с. — ISBN 978-5-469-01136-1.
- Эрик Фримен, Элизабет Фримен. Паттерны проектирования = Head First Design Patterns. — СПб.: Питер, 2011. — 656 с. — ISBN 978-5-459-00435-9.
Примечания[править | править код]
- ↑ Паттерн Observer . Дата обращения: 13 июня 2013. Архивировано 13 июня 2013 года.
- ↑ 1 2 Шаблон разработки Observer . Дата обращения: 13 июня 2013. Архивировано 13 июня 2013 года.
- ↑ Паттерн наблюдатель (Observer) . Дата обращения: 4 ноября 2019. Архивировано 4 ноября 2019 года.