Паттерн “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>(); 	} 

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

Плюсы:

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

Минусы:

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

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/201908/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *