Спостерігач (шаблон проєктування)Спостерігач, Observer — поведінковий шаблон проєктування. Також відомий як «підлеглі» (Dependents), «видавець-передплатник» (Publisher-Subscriber). ПризначенняВизначає залежність типу «один до багатьох» між об'єктами таким чином, що при зміні стану одного об'єкта всіх залежних від нього сповіщають про цю подію. Переваги
Недоліки
УстрійПри реалізації шаблону «спостерігач» зазвичай використовуються такі класи:
При зміні спостережуваного об'єкту, оповіщення спостерігачів може бути реалізоване за такими сценаріями:
Область застосуванняШаблон «спостерігач» застосовується в тих випадках, коли система володіє такими властивостями:
Цей шаблон часто застосовують в ситуаціях, в яких відправника повідомлень не цікавить, що роблять одержувачі з наданою їм інформацією. Простими словамиОб'єкт володіє важливими даними і на нього підписані спостерігачі. Кожен спостерігач має можливість обновити ці дані а інші спостерігачі повинні отримати про це сповіщення і обновитись в слід якщо це необхідно. Спостерігач не повинен запитувати об'єкт з певною періодичністю, він завжди знає що його дані актуальні. Зв'язок із іншими патернами
ПрикладиКод програми на мові 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);
}
}
}
Код програми на мові C# (Рішення на основі інтерфейсів)
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";
}
}
}
Код програми на мові C# (Рішення на основі подій)
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";
}
}
}
Код програми на мові C# (Рішення на основі сервісу)
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;
}
}
}
РеалізаціїШаблон Спостерігач реалізований в численних бібліотеках і системах, включаючи майже всі інструментарії графічних інтерфейсів користувача. Деякі з найпомітніших реалізацій шаблону перелічені нижче:
BASIC
C
C++
C#
PHP
Інше
Посилання
Джерела
ЛітератураАлан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.
|