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

в 19:31, , рубрики: паттерны проектирования, Проектирование и рефакторинг, метки: ,

Признаюсь честно, описание этого паттерна мне не встречалось, соответственно его название я выдумал. Если у кого есть информация о правильном названии, буду очень рад услышать. Паттерн не привязан к языку но в данной статье я буду использовать C#.

Картинка для привлечения внимания:

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

Итак представим себе систему состоящую из сервиса, который предоставляет другим частям системы отслеживать набор объектов. Это может быть сервис симуляции предоставляющий список симулируемых объектов или любой другой похожий.

Объекты порождаемые и уничтожаемые системой:

	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

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js