Пользователь
0,0
рейтинг
12 ноября 2013 в 23:31

Разработка → Паттерн «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>();
	}


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

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


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


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


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

Всем спасибо за внимание!
Семёнов Альберт @mynameco
карма
21,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (22)

  • +9
    Голосовалка нужна — кто из троих изображенных на картинке VIP слушатель. :)
    • +2
      Я добавил пояснение в конце, в данном случае все трое являются випами =)
  • +5
    Вы их тех кто писал в учебном заведении сначала код, а потом по нему составлял блок схемы?
    • +2
      Я самоучка =) Этот паттерн юзали умные дядьки у нас на работе давным давно и названия он не имел.
      В книге банды 4 его нету, в книге «шаблоны корпоративных приложений» фаулера тоже нету. В книге совершенный код его нету. В книгах про 20 советов тоже небыло.
  • +4
    Вот что бывает, если слушать машинный аккумулятор.
  • 0
    Есть в .NET такой класс — ObservableCollection, знаете?
    • 0
      Кхм, и как ObservableCollection поможет отслеживать изменения объектов которые в ней содержатся? Она ведь сигнализирует только при изменении списка, но не его элементов.
      • 0
        А как ваш паттерн реализует мониторинг изменения самих элементов?
        • 0
          My bad. Наивно предположил, что в RegisterItem происходит подписка на события объекта, и потом эти события передаются дальше с помощью ServiceStateChanged.
          Как вы понимаете, это — не мой паттерн.
      • 0
        Если я изменю состояние любого IObject, сервис об этом не узнает.
    • 0
      Этот класс не решает тот паттерн который я описал, а даже если бы и решал то от этого паттерн не перестал бы быть паттерном.
      • +3
        Паттерны головного мозга. Смысл выделять это в отдельный паттерн? По-моему, это просто вариант реализации «наблюдателя» для конкретной задачи.

        «VIP слушатель» — сразу думаешь о «наблюдателе» в котором есть VIP слушатель, приоритетнее остальных
        Особенность с еще одним уведомлением об изменении нуууууууу… я бы не использовал такие опасные вещи, сайд эффекты будут восхитительными :)

        Не принимайте близко к сердцу :)
        • –3
          У меня нет сердца, я программист. Снизу пояснил суть паттерна. Это модифицированный наблюдетель с возможностью получить события произошедшие до момента подписки.
  • 0
    ой, ошибся...
  • 0
    Можете более подробно описать проблему, которую решает паттерн?
    • 0
      Это модифицированый наблюдатель, он решает проблему получения событий, которые произошли до подписки наблюдателя и соответствено были пропущены. В данном случае это события появления объектов в каком либо сервисе.
      • 0
        Наконец-то понял смысл. Записывать события и отправлять всю историю новым подписчикам. Но все ещё не ясно почему ВИП
        • 0
          Наблюдаемый объект обслуживает наблюдателя отдельно, формируя для него отдельный набор событий. Но повторюсь, название я придумал, если есть идеи других названий то велком.
          • 0
            «Наблюдательный пункт» («Observing Station»).
  • +2
    В момент подписки подписчика завалит уже вылупившимися объектами? С одной стороны вроде как и плохо (подписчик, возможно, ожидает объекты в момент их появления, а старые объекты его может не интересуют), с другой — хорошо (не надо перед подпиской запрашивать все объекты, синхронизаций нелепых меньше). Интересно, подумаю на досуге, но всё же что-то мне в этом недопуле объектов с прикрученной нотификацией о изменении не нравиццо.
    • 0
      Все верно, есть места где такое не нужно или опасно, а есть места где это очень удобно. Например если есть один симулятор и много обслуживающих слушателей, которые можно отключать и подключать в рантайме, то при использовании такого подхода разработка упрощается.
  • +3
    По хорошему, взять бы и создать на гитхабе репозиторий с проектом, а ещё бы и юнит тесты в него добавить.
    Тогда бы и вопросов не возникало и было бы очень наглядно.

    А по поводу паттерна, могу сказать, что патерны редко используются из книги. Всегда используется своя вариция паттерна.
    Вот ещё одна «своя» вариация на тему Observer Pattern.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.