C#
.
Картинка для привлечения внимания:
Итак представим себе систему состоящую из сервиса, который предоставляет другим частям системы отслеживать набор объектов. Это может быть сервис симуляции предоставляющий список симулируемых объектов или любой другой похожий.
Объекты порождаемые и уничтожаемые системой:
public interface IObject { }
Сервис, предоставляющий доступ к объектам:
public delegate void ServiceChangedHandle(IService sender, IObject item, bool injected); public interface IService { IEnumerable<IObject> Items { get; } event ServiceChangedHandle OnServiceChanged; }
Тем системам, которым необходимо работать с объектами, подписываются на событие, чтобы отслеживать появление новых объектов и исчезновение текущих.
Типичный пример слушателя:
public class Listener { public void Initialise() { foreach (var item in service.Items) RegisterItem(item); service.OnServiceChanged += OnServiceChanged; } public void Shutdown() { service.OnServiceChanged -= OnServiceChanged; foreach (var item in service.Items) UnregisterItem(item); } private void OnServiceChanged(IService sender, IObject item, bool injected) { if (injected) RegisterItem(item); else UnregisterItem(item); } private void RegisterItem(IObject item) { ... } private void UnregisterItem(IObject item) { ... } private IService service; }
При активации слушателя он обрабатывает все объекты которые уже присутствуют в сервисе а после этого подписывается на изменения списка объектов. При завершении работы слушателя необходимо произвести обратные действия.
При многопоточном программировании все усложняется, потому что между опросом и подпиской, список объектов может изменится. Сервис должен будет предоставить объект синхронизации а слушатель заблокирует изменение списка между обходом списка и подпиской.
Сервис с поддержкой многопоточности:
public interface IService { ... // Объект для синхронизации object SyncRoot { get; } }
Слушатель с поддержкой многопоточного сервиса (Внутренняя синхронизация опущенна):
public class Listener { public void Initialise() { // Добавляем синхронизацию lock (service.SyncRoot) { foreach (var item in service.Items) RegisterItem(item); service.OnServiceChanged += OnServiceChanged; } } public void Shutdown() { // Добавляем синхронизацию lock (service.SyncRoot) { service.OnServiceChanged -= OnServiceChanged; foreach (var item in service.Items) UnregisterItem(item); } } ... }
Можно немного упростить систему подписки, если гарантировать что в момент подписки и отписки в сервисе нет ни одного объекта. Такую гарантию дать сложно, особенно в системах где время появления сервисов не определенно.
Но можно эмулировать эту гарантию для каждого подписчика, в этом и суть паттерна. При подписке, сервис будет принудительно посылать событие появления объекта для всех уже существующих объектов а при отписке, посылать событие исчезновения. При этом слушатель упрощается, причем для многопоточного и однопоточного варианта он будет выглядеть одинаково.
Подписчик для многопоточного и однопоточного варианта сервиса (Внутренняя синхронизация опущена):
public class Listener { public void Initialise() { service.OnServiceChanged += OnServiceChanged; } public void Shutdown() { service.OnServiceChanged -= OnServiceChanged; } ... }
Реализации сервиса для однопоточного варианта:
public class Service : IService { ... public event ServiceChangedHandle OnServiceChanged { add { // Эмулируем добавление объектов для подписчика foreach (var item in items) value(this, item, true); // Непосредственная подписка eventHandle += value; } remove { // Непосредственная отписка eventHandle -= value; // Эмулируем исчезновение объектов foreach (var item in items) value(this, item, false); } } private ServiceChangedHandle eventHandle; private List<IObject> items = new List<IObject>(); }
Как и у любого паттерна у этого варианта слушателя есть свои плюсы, минусы и область применения.
Плюсы:
- Подписчики упрощаются, достаточно простой подписки и отписки
- Одинаковый код как для многопоточного так и для однопоточного варианта
Минусы:
- При использовании нужно знать эту особенность у сервиса, чтобы объекты не обрабатывать два раза
Из минусов и плюсов можно выделить область применения паттерна:
- Наличие небольшого количества сервисов и большого количества подписчиков
- Внутренний продукт компании или сугубо личный, предоставлять наружу такое поведение опасно
- Строгая дисциплина проектирования и разработки. Каждый разработчик должен знать о таком поведении и знать где конкретно используется этот паттерн
Всем спасибо за внимание!
ссылка на оригинал статьи http://habrahabr.ru/post/201908/
Добавить комментарий