Wprowadzenie

W artykule tym zostanie omówiony wzorzec projektowy Obserwator oraz sposób jego użycia w języku C#.

Wielokrotnie w naszej aplikacji może pojawić się potrzeba dokonania aktualizacji danej części aplikacji zmianą status z innej cześci aplikacji. Jedną z możliwości jest przygotowanie specjalnego modułu odbioru powiadomień, który wielokrotnie sprawdza czy nadeszły jakieś aktualizacje. Takie podejście ma jednak dwa główne problemy. Po pierwsze, takie sprawdzanie aktualizacji obciąża procesor a drugi to interwał pomiędzy kolejnymi sprawdzeniami, nie uzyskamy informacji o aktualizacji w trybie natychmiastowym.

Rozwiązaniem takiego problemu jest zastosowanie wzorca Obserwatora. Poniżej diagram klas dla wzorca projektowego:

Wzorzec projektowy obserwatora

Omówmy wszystkie klasy jedna po drugiej:

  • Subject - klasa ta zawiera listę wszystkich obserwatorów i dostarcza funkcjonalność pozwalającą nam dodawanie lub usuwanie obserwatora. Klasa ta jest również odpowiedzialna za aktualizacje obserwatorów, gdy dochodzi do jakiejś zmiany. W tym celu, w poniższym przykładzie została zaimplementowana klasa ASubject;
  • ConcreteSubject - klasa ta jest klasą implementującą Subject. Klasa ta jest encją, której zmiana wpłynie na wszystkie inne obiekty. W poniższym przykładzia została zaimplementowana klasa Dummy w celu osiągnięcia tego samego działania;
  • Observer - określa interfejs definiujący metody, które powinny być wywołane, gdy dochodzi do zmiany. W przykładowym projekcie jest to IObserver;
  • ConcreteObserver - to jest klasa, która musi aktualizować samą siebie wraz ze zmianą. Klasa ta musi zaimplementować Observer oraz zarejestrować siebie z ConcretSubject, aby otrzymywać powiadomienia. W przykładowej aplikacji do osiągnięcia tego celu została użyta klasa Shop.

Poniżej ten sam diagram, ale po zastosowaniu mojej implementacji:

Wzorzec obserwatora

Zanim przeniesiemy się do właściwego kodu chciałbym wyjaśnić jeszcze jedną rzecz. Na platformie .NET dostępne są delegaty, które są bardzo dobrym przykładem wzorca Obserwatora. Tak naprawdę nie musimy implementować całego wzorca w języku C#, aby używać delegatów do tej samej funkcjonalności – w ramach przykładu dokonamy tej implementacji, aby dobrze zrozumieć ten wzorzec. Ponadto, dokonamy implementacji delegatów w taki sposób, aby współpracowały z wzorcem Obserwatora. Przejdźmy zatem do kodu.


Aplikacja

Interfejs IObserver

namespace ObserverPattern
{
	interface IObserver
	{
		void Update(float price);
	}
}
Klasa Shop
namespace ObserverPattern
{
	class Shop : IObserver
	{
		string name;
		float price = 0.0f; // wartość domyślna
		public Shop(string name)
		{
			this.name = name;
		}
		public void Update(float price)
		{
			this.price = price;
			Console.WriteLine($"Cena produktu {this.name} to {this.price}");
		}
	}
}
Klasa abstrakcyjna ASubject
using System.Collections;
namespace ObserverPattern
{
    class ASubject
	{
		// Pierwszy sposób implementacji (1)
		ArrayList list = new ArrayList();
		// Drugi sposób implementacji (2)
		public delegate void StatusUpdate(float price);
		public event StatusUpdate OnStatusUpdate = null;
		public void Attach(Shop product)
		{
			// dodajemy obserwatora dla sposobu nr 1
			list.Add(product);
		}
		public void Detach(Shop product)
		{
			// usuwamy obserwatora z naszej listy dla sposobu 1
			list.Remove(product);
		}
		public void Attach2(Shop product)
		{
			// dodajemy obserwatora dla sposobu nr 2
			OnStatusUpdate += new StatusUpdate(product.Update);
		}
		public void Detach2(Shop product)
		{
			// usuwamy obserwatora dla sposobu 2
			OnStatusUpdate -= new StatusUpdate(product.Update);
		}
		public void Notify(float price)
		{
			// dla pierwszego sposobu informujemy obserwatora o zmianie
			foreach (Shop p in list)
			{
				p.Update(price);
			}
			// dla drugiego sposobu informujemy obserwatora o zmianie
			if (OnStatusUpdate != null)
				OnStatusUpdate(price);
		}
	}
}
Przykładowy produkt
namespace ObserverPattern
{
	class DummyProduct : ASubject
	{
		public void ChangePrice(float price)
		{
			Notify(price);
		}
	}
}
Przykładowe użycie
using System;
namespace ObserverPattern
{
    class Program
	{
		static void Main(string[] args)
		{
			DummyProduct prod = new DummyProduct();
			// Ustalamy 4 sklepy, które będą stosowały zaktualizowane ceny
			Shop shop1 = new Shop("Shop1");
			Shop shop2 = new Shop("Shop2");
			Shop shop3 = new Shop("Shop3");
			Shop shop4 = new Shop("Shop4");
			// Dodajemy dwa sklepy dla 1 sposobu
			prod.Attach(shop1);
			prod.Attach(shop2);
			// Dodajemy dwa sklepy dla 2 sposobu
			prod.Attach2(shop3);
			prod.Attach2(shop4);
			// Zmienimy teraz cenę produktu, powinna zmienić się automatycznie we wszystkich sklepach
			prod.ChangePrice(23.0f);
			// Teraz usuniemy dwa sklepy, którymi się nie interesujemy
			prod.Detach(shop2);
			prod.Detach2(shop4);
            // A następnie ponownie zmienimy cenę
			prod.ChangePrice(8.0f);
			Console.ReadKey();
		}
	}
}