Obserwator (observer)

Dzisiaj weźmiemy pod nóż wzorzec projektowy należący do grupy wzorców czynnościowych, mianowicie obserwator. Jest często używany w projektach, a jego prostota pozwala na implementację nawet początkującym programistom. To chyba wystarczy, aby każdego zachęcić do zapoznania się z tym wzorcem.

Obserwator (observer)

Zasada działania jego działania jest bardzo prosta. Wyobraźmy sobie sytuację, w której obiekt (obserwowany) chcę informować o swoim stanie, inne zainteresowane obiekty (obserwatorów). Zobrazujmy tę sytuację na podstawie jakiegoś czujnika (obserwowany) w fabryce, czujnik odczytuje np. temperaturę (dana, którą zainteresowane są inne obiekty). Detektor posiada informację o urządzeniach, które są zainteresowane jego wskazaniem (obserwatorzy), urządzenia te są połączone z nim kablem (programiści zamiast kabli używają listy, w której przechowują referencję do zainteresowanych obiektów), kiedy temperatura się zmieni czujnik wysyła informacje do urządzeń, które pobierają informacje o aktualnym odczycie. Sondy, czy też urządzenia w fabrykach wykonywane są według odpowiedniego schematu, tak i nasze klasy, które reprezentowane są przez obserwatora i obserwowanego budowane są na podstawie szablonów, zapisanych zwykle w interfejsach. Gwarantuje nam to podstawową zgodność zapewniającą współpracę oraz niezależność pomiędzy kolejnymi implementacjami interfejsu obserwatora i interfejsu obserwowanego. Tak samo będzie działał nasz kod, który za chwilę napiszemy.

Diagram klas

Wzorzec projektowy obserwator

Wzorzec projektowy obserwator

Kod źródłowy

Zajmijmy się teraz implementacją naszej symulacji z czujnikiem i obserwujących go urządzeń. Zacznijmy od dodania do projektu konsolowego nowego pliku IObserwowany.cs z intefejsem.

namespace Obserwator
{
    public interface IObserwowany
    {
        int jakasWlasciwosc { get; set; }

        void dodajObserwatora(IObserwator o);
        void usunObserwator(IObserwator o);
        void powiadomObserwatorow();
    }
}

Powyższy interfejs będą implementować wszystkie klasy, których obiekty będą obserwowane, czyli w naszym przykładzie czujniki. Dzięki zastosowaniu interfejsu jesteśmy pewni, że klasy będą posiadały podstawową funkcjonalność wymaganą przy wzorcu obserwator, dodałem też dla „urozmaicenia” jedną właściwość w interfejsie 🙂

Został nam do napisania jeszcze jeden interfejs, który będą implementować obserwatorzy, w naszym przypadku urządzenia.

namespace Obserwator
{
    public interface IObserwator
    {
        void aktualizacjaDanych();
    }
}

Użyjmy utworzony przed chwilą interfejs w klasie, niech będzie to klasa imitująca pracę czujnika.

using System.Collections.Generic;

namespace Obserwator
{
    class Czujnik : IObserwowany
    {
        //lista obiektów, które będą obserwować obiekty tej klasy 
        private List<IObserwator> _listaObserwatorow = new List<IObserwator>();

        public int jakasWlasciwosc { get; set; }
        private int temperatura;

        public void dodajObserwatora(IObserwator o)
        {
            _listaObserwatorow.Add(o);
        }

        public void usunObserwator(IObserwator o)
        {
            _listaObserwatorow.Remove(o);
        }

        public void powiadomObserwatorow()
        {
            foreach(var item in _listaObserwatorow)
            {
                item.aktualizacjaDanych();
            }
        }

        public int pobierzTemperature()
        {
            return temperatura+15;
        }

        public void dokonajPomiaru(int wartosc)
        {
            temperatura = wartosc;
        }
    }
}

Podstawą jest to, że konkretna obserwowana klasa jest budowana w oparciu o interfejs IObserwowany. Widzimy, że zawiera listę obiektów nią zainteresowanych, którą manipulują odpowiednie metody, dodałem także metodę pobierzTemperature(), które „odsłania” zawartość zmiennej temperatura.

Została nam jeszcze do utworzenia klasa Obserwatora, jak już wcześniej zaplanowaliśmy, klasa będzie symulować prace urządzenia, nazwijmy ją Urzadzenie.

using System;
namespace Obserwator
{
    class Urzadzenie : IObserwator
    {
        //prywatne pole do przechowywanie pobranej temperatury
        private int pobranaTemperatura;
        //referencja do obserwowanego obiektu
        private Czujnik czujnikTemperatury;

        //wraz z definicja obiektu przypisywany mu jest czujnik
        public Urzadzenie(Czujnik o)
        {
            czujnikTemperatury = o;
        }

        //metoda pobierajaca temperature
        public void aktualizacjaDanych()
        {
            pobranaTemperatura = czujnikTemperatury.pobierzTemperature();
            Console.WriteLine("Obiekt pobral temperature rowna {0}", pobranaTemperatura);
        }
    }
}

Tak jak zwykle opis zawarłem w komentarzach, widzimy powyżej, że konkretnyObserwator obserwuje konkrektnegoObserwowanego oraz posiada referencję do obiektu. Pozostało nam jeszcze zasymulowanie pracy czujnika i urządzeń w głównej metodzie main().

using System;

namespace Obserwator
{
    class Program
    {
        static void Main(string[] args)
        {
            //tworzymy obiekt klasy Czujnik
            Czujnik czujnikNumerDwa = new Czujnik();

            //dodajemy obiekty zainteresowane odczytem z obiektu czujnikNumerDwa
            Urzadzenie urzadzenie1 = new Urzadzenie(czujnikNumerDwa);
            Urzadzenie urzadzenie2 = new Urzadzenie(czujnikNumerDwa);

            czujnikNumerDwa.dodajObserwatora(urzadzenie1);
            czujnikNumerDwa.dodajObserwatora(urzadzenie2);

            //odczytujemy temperature
            czujnikNumerDwa.dokonajPomiaru(22);

            //zaszla zmiana, wiec powiadamy obserwatorow
            czujnikNumerDwa.powiadomObserwatorow();

            //zalozmy ze jeden z obserwatorow nie jest juz zainteresowany temperatura
            czujnikNumerDwa.usunObserwator(urzadzenie1);

            //odczytujemy temperature
            czujnikNumerDwa.dokonajPomiaru(12);

            //zaszla zmiana, wiec powiadamy obserwatorow
            czujnikNumerDwa.powiadomObserwatorow();


            Console.ReadLine();
        }
    }
}

Wyjście:

Wyjście

Wyjście

Podsumowanie

Jak przekonaliśmy się wzorzec obserwator jest bardzo prostym szablonem oraz ma ciekawą funkcjonalność. Każdy człowiek poznawszy jego działanie potrafiłby wymienić od ręki kilka sytuacji, gdzie z powodzeniem można by się nim posłużyć. Mam nadzieję, że artykuł był z serii Keep It Simple Stupid :), a być może komuś się przyda w pracy.

Wpis należy do serii postów o wzorcach projektowych.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *