Шаблон Наблюдатель определяет отношение между объектами таким образом, что при изменении состояния одного объекта все зависящие от него объекты оповещаются об этом. Основное назначение шаблона Наблюдатель – реализация системы работы с событиями.
Разберём устройство шаблона Наблюдатель (рис. 13). Объект класса Subject – наблюдаемый объект – имеет некоторое состояние, изменение которого предполагается отслеживать. Для этого в классе Subject описывается коллекция объектов-наблюдателей и методы добавления и удаления элементов коллекции. Каждый объект-наблюдатель реализует интерфейс IObserver. При изменении состояния наблюдаемого объекта вызывается метод NotifyObservers(). Этот метод итерируется по коллекции наблюдателей и вызывает у каждого из них метод HandleEvent() (возможно, передавая в качестве аргумента состояние наблюдаемого объекта).
Рис. 13. Дизайн шаблона Наблюдатель.
Далее приводится пример кода, демонстрирующего реализацию шаблона Наблюдатель. Заметим, что в примере намеренно не используются события C#.
|
|
using System;
using System.Collections.Generic;
// аналог Subject
public class Stock
{
private int _price;
private IList<IInvestor> _investors = new List<IInvestor>();
public int Price
{
get { return _price; }
set
{
if (_price!= value)
{
_price = value;
Notify();
}
}
}
public Stock(int price)
{
_price = price;
}
public void Attach(IInvestor investor)
{
_investors.Add(investor);
}
public void Detach(IInvestor investor)
{
_investors.Remove(investor);
}
public void Notify()
{
foreach (var investor in _investors)
{
investor.Update(this);
}
}
}
// интерфейс, аналогичный IObserver
public interface IInvestor
{
void Update(Stock stock);
}
// конкретный наблюдатель
public class Investor: IInvestor
{
public string Name { get; private set; }
public Investor(string name)
{
Name = name;
}
public void Update(Stock stock)
{
Console.WriteLine("Notified {0} of change to {1}",
Name, stock.Price);
}
}
public class ObserverExample
{
public static void Main()
{
var ibm = new Stock(120);
ibm.Attach(new Investor("Soros"));
ibm.Attach(new Investor("Berkshire"));
ibm.Price = 120;
ibm.Price = 121;
ibm.Price = 125;
}
}
Для унификации работы с шаблоном Наблюдатель платформа.NET предлагает два интерфейса. Интерфейс System.IObserver<T> реализуется наблюдателем. Этот интерфейс содержит методы, уведомляющие о новом событии, об ошибке в наблюдаемом объекте и о прекращении генерации событий.
public interface IObserver<in T>
{
void OnCompleted();
void OnError(Exception error);
void OnNext(T value);
}
Интерфейс System.IObservable<T> реализуется наблюдаемым объектом. В интерфейсе описан единственный метод Subscribe() для добавления наблюдателя. Чтобы удалить наблюдателя, нужно вызвать метод Dispose() у объекта, возвращённого методом Subscribe().
public interface IObservable<out T>
{
IDisposable Subscribe(IObserver<T> observer);
}
Изменим пример, представленный выше, используя типовые интерфейсы.
using System;
using System.Collections.Generic;
public class Stock: IObservable<int>
{
private int _price;
private IList<IInvestor> _investors = new List<IInvestor>();
|
|
public int Price
{
get { return _price; }
set
{
if (_price!= value)
{
_price = value;
Notify();
}
}
}
public Stock(int price)
{
_price = price;
}
public IDisposable Subscribe(IObserver<int> investor)
{
_investors.Add(investor);
return new Unsubscriber(_investors, investor);
}
public void Notify()
{
foreach (var investor in _investors)
investor.OnNext(_price);
}
private class Unsubscriber: IDisposable
{
private readonly IObserver<int> _investor;
private readonly IList<IObserver<int>> _investors;
public Unsubscriber(IList<IObserver<int>> investors,
IObserver<int> investor)
{
_investors = investors;
_investor = investor;
}
public void Dispose()
{
_investors.Remove(_investor);
}
}
}
public class Investor: IObserver<int>
{
public string Name { get; private set; }
public Investor(string name)
{
Name = name;
}
public void OnNext(int value)
{
Console.WriteLine("Notified of change to {0}", value);
}
public void OnError(Exception error) { }
public void OnCompleted() { }
}
public class ObserverExample
{
public static void Main()
{
var ibm = new Stock(120);
ibm.Subscribe(new Investor("Soros"));
ibm.Subscribe(new Investor("Berkshire"));
ibm.Price = 120;
ibm.Price = 121;
ibm.Price = 125;
}
}