Шаблоны проектирования в .NET: Наблюдатель (Observer)

Design

Хороший объектно-ориентированный дизайн подчеркивает важность инкапсуляции и слабой связанности. Иными словами, классы должны сохранять внутренние детали приватными, а также сводить к минимуму их строгие зависимости. В большинстве приложений, классы не работают в изоляции, они взаимодействуют со многими другими классами. Распространенным сценарием взаимодействия классов является случай, когда один класс (Наблюдатель) должен быть оповещен, когда что-то меняется в другом (Субъект). Например, некоторым контролам Windows ® Forms, возможно, потребуется обновить свое отображение после нажатия кнопки. Простым решением было бы иметь Субъект, который вызывает определенный метод наблюдателя при изменении своего состояния. Однако, при этом возникает множество проблем. Теперь, поскольку Cубъект должен знать, какой метод вызвать, он тесно связан с конкретным Наблюдателем. Кроме того, если вам нужно добавить более одного наблюдателя, вы должны продолжать добавлять к Субъекту код для каждого вызова метода. Если количество наблюдателей меняется динамически, это становится еще более сложным. У вас быстро образуется беспорядок, который трудно поддерживать.

Применение шаблона Наблюдатель (Observer) помогает решить эту проблему эффективно. Вы можете отделить Субъект от Наблюдателей, так что Наблюдатели любого типа можно легко добавлять и удалять, как на стадии дизайна, так и во время выполнения. Субъект содержит список заинтересованных наблюдателей. Каждый раз, когда состояние субъекта изменяется, он вызывает метод Notify на каждом Наблюдателе. Ниже показан пример реализации. Все классы, предназначенные для работы в качестве Наблюдателей, реализуют интерфейс ICanonicalObserve, а все Субъекты должны быть производными от CanonicalSubjectBase. Если новый Наблюдатель хочет наблюдать за Субъектом, метод Add легко справляется с этим без изменения кода в классе Субъекта. Отметим также, что каждый Субъект напрямую зависит только от интерфейса ICanonicalObserver, а не какого-либо конкретного Наблюдателя.

public abstract class CanonicalSubjectBase
	{
		private ArrayList _observers = new ArrayList();
		public void Add(ICanonicalObserver o)
		{
			_observers.Add(o);
		}

		public void Remove(ICanonicalObserver o)
		{
			_observers.Remove(o);
		}

		public void Notify()
		{
			foreach(ICanonicalObserver o in _observers)
			{
				o.Notify();
			}
		}
	}

	public interface ICanonicalObserver
	{
		void Notify();
	}

	public class CanonicalSubject : CanonicalSubjectBase
	{
		public CanonicalSubject()
		{
		}

		public void ChangeState()
		{
			Notify();
		}

		public static void TestCanonicalObserver()
		{
			Console.WriteLine("Canonical Observer");
			CanonicalSubject s = new CanonicalSubject();
			CanonicalObserver o1 = new CanonicalObserver(s);

			s.ChangeState();

			Console.WriteLine("Done.\n");
		}
	}

	public class CanonicalObserver : ICanonicalObserver
	{
		public CanonicalObserver(CanonicalSubject s)
		{
			s.Add(this);
		}

		public void Notify()
		{
			Console.WriteLine("Canonical Observer - Notify");
		}
	}

Хотя шаблон Наблюдатель «Банды четырех» решает некоторые из этих проблем, все еще существует ряд препятствий, так как Cубъекты должны наследовать от определенного базового класса, а Наблюдатели должны реализовывать специальный интерфейс. Решение всплывает, вспоминая пример кнопки Windows Forms. .NET Framework предоставляет делегаты и события, чтобы решить эти проблемы. Если вы программировали на ASP.NET или Windows Forms, то вы, вероятно, работали с событиями и обработчиками событий. События выступают в качестве Субъекта, в то время как делегаты выступают в качестве Наблюдателей. Ниже показан пример паттерна наблюдатель, используя события.

public delegate void Event1Hander();
	public delegate void Event2Handler(int a);
	public class Subject
	{
		public Subject()
		{
		}

		public Event1Hander Event1;
		public Event2Handler Event2;

		public void RaiseEvent1()
		{
			if (Event1 != null)
			{
				Event1();
			}
		}

		public void RaiseEvent2()
		{
			if (Event2 != null)
			{
				Event2(6);
			}
		}

		public static void TestEventObserver()
		{
			Console.WriteLine("Event Observer");
			Subject s = new Subject();
			Observer1 o1 = new Observer1(s);
			Observer2 o2 = new Observer2(s);

			s.RaiseEvent1();
			s.RaiseEvent2();

			Console.WriteLine("Done.\n");
		}
	}

	public class Observer1
	{
		public Observer1(Subject s)
		{
			s.Event1 += new Event1Hander(HandleEvent1);
			s.Event2 += new Event2Handler(HandleEvent2);
		}

		public void HandleEvent1()
		{
			Console.WriteLine("Observer 1 - Event 1");
		}

		public void HandleEvent2(int a)
		{
			Console.WriteLine("Observer 1 - Event 2");
		}
	}

	public class Observer2
	{
		public Observer2(Subject s)
		{
			s.Event2 += new Event2Handler(HandleEvent2);
		}

		public void HandleEvent2(int a)
		{
			Console.WriteLine("Observer 2 - Event 2");
		}
	}

Windows Forms контрол Button предоставляет событие Click, которое вызывается, каждый раз когда была нажата кнопка. Любой класс предназначенный реагировать на это событие должен просто зарегистрировать делегата с этим событием. Класс Button не зависит от любых потенциальных Наблюдателей, и каждый Наблюдатель должен знать только правильный тип делегата для события (в данном случае EventHandler). Так как EventHandler является типом делегата, а не интерфейса, каждому наблюдателю не нужно реализовывать дополнительных интерфейсов. Предположим, он уже содержит метод с совместимой сигнатурой — тогда необходимо только зарегистрировать метод с событием Субъекта. С помощью делегатов и событий, шаблон Наблюдатель позволяет отделить Субъекты и их Наблюдателей.

Источник: http://msdn.microsoft.com/en-us/magazine/cc188707.aspx#S1

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

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

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>