Признаюсь честно, описание этого паттерна мне не встречалось, соответственно его название я выдумал. Если у кого есть информация о правильном названии, буду очень рад услышать. Паттерн не привязан к языку но в данной статье я буду использовать 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>();
}
Как и у любого паттерна у этого варианта слушателя есть свои плюсы, минусы и область применения.
Плюсы:
- Подписчики упрощаются, достаточно простой подписки и отписки
- Одинаковый код как для многопоточного так и для однопоточного варианта
Минусы:
- При использовании нужно знать эту особенность у сервиса, чтобы объекты не обрабатывать два раза
Из минусов и плюсов можно выделить область применения паттерна:
- Наличие небольшого количества сервисов и большого количества подписчиков
- Внутренний продукт компании или сугубо личный, предоставлять наружу такое поведение опасно
- Строгая дисциплина проектирования и разработки. Каждый разработчик должен знать о таком поведении и знать где конкретно используется этот паттерн
Всем спасибо за внимание!
Автор: mynameco