...

вторник, 12 ноября 2013 г.

Паттерн “VIP слушатель”

Признаюсь честно, описание этого паттерна мне не встречалось, соответственно его название я выдумал. Если у кого есть информация о правильном названии, буду очень рад услышать. Паттерн не привязан к языку но в данной статье я буду использовать 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>();
}


Как и у любого паттерна у этого варианта слушателя есть свои плюсы, минусы и область применения.


Плюсы:



  • Подписчики упрощаются, достаточно простой подписки и отписки

  • Одинаковый код как для многопоточного так и для однопоточного варианта


Минусы:



  • При использовании нужно знать эту особенность у сервиса, чтобы объекты не обрабатывать два раза


Из минусов и плюсов можно выделить область применения паттерна:


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий