Как стать автором
Обновить

Паттерн «VIP слушатель»

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


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

Плюсы:
  • Подписчики упрощаются, достаточно простой подписки и отписки
  • Одинаковый код как для многопоточного так и для однопоточного варианта


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


Из минусов и плюсов можно выделить область применения паттерна:
  • Наличие небольшого количества сервисов и большого количества подписчиков
  • Внутренний продукт компании или сугубо личный, предоставлять наружу такое поведение опасно
  • Строгая дисциплина проектирования и разработки. Каждый разработчик должен знать о таком поведении и знать где конкретно используется этот паттерн


Update1
Некоторые пояснения:
В шарпе уже реализован паттерн “Наблюдатель”, вместо наблюдателя и наблюдаемого мы имеем событие и подписчиков.
VIP не синоним слова “один” а синоним слова “особый”. В данном случае все подписчики являются особыми, потому что наблюдаемый объект для каждого отдельного наблюдателя ведет себя особо. А именно, генерит события, которые наблюдатель мог пропустить или не дождаться.

Всем спасибо за внимание!
Теги:
Хабы:
Всего голосов 41: ↑23 и ↓18+5
Комментарии22

Публикации

Истории

Работа

Ближайшие события

Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область