obserwator

Wzorzec projektowy Obserwator

Wprowadzenie

Wzorce projektowe to rozwiązania popularnych problemów programistycznych. Wyróżniamy następujące grupy wzorców:

  • wzorce kreacyjne, które zajmują się tworzeniem obiektów, np. Fabryka, Budowniczy, Singleton.
  • wzorce strukturalne, które pomagają grupować obiekty w większe struktury, np. Adapter, Dekorator.
  • wzorce behawioralne, które wspomagają definiowanie ścieżek komunikacyjnych między obiektami, np. Obserwator, Strategia.

Obserwator (Observer) jest behawioralnym wzorcem projektowym. Jego głównym zadaniem jest przekazywanie informacji o zmianie stanu obiektu innym zainteresowanym obiektom. Stosuje się go głównie w sytuacjach, w której mamy do czynienia z relacją jeden do wielu, gdzie wiele obiektów musi być informowana o zmianie stanu obiektu głównego.

Problem i rozwiązanie

Budujemy aplikację prognozy pogody, która ma dostarczać najnowsze dane do różnych mediów. Każda z platform chce na bieżąco otrzymywać informacje o zmianach pogody.

Bez zastosowania wzorca projektowego aplikacja musiałaby na sztywno powiązać klasę prognozy pogody z każdą klasą reprezentującą media. Każde usunięcie lub dodanie nowej platformy ingerowałoby w istniejący kod klasy prognozy pogody.

Wzorzec Obserwator umożliwia informowanie dowolnej liczby mediów (obserwatorów) bez wiedzy na temat ich klas. W dowolnym momencie można bezinwazyjnie dodać nową platformę lub usunąć istniejącą.

Schemat działania

Poniższy diagram UML dzieli się na dwie zasadnicze części:

  • lewa część odpowiedzialna za obiekt obserwowany (podmiot) – obiekt klasy WeatherForecast implementujący interfejs ObservableInterface.
  • prawa część odpowiedzialna za obserwatorów obiektu – obiekty klas InternetNews, TvNews i RadioNews, implementujące interface ObserverInterface.

Implementacja

Struktura folderów i plików projektu

  • observer
    • alert
      • InternetNews.php
      • ObserverInterface.php
      • RadioNews.php
      • TvNews.php
    • weather
      • ObservableInterface.php
      • WeatherForecast.php
    • index.php

Kod źródłowy

Plik ObserverInterface.php

<?php
declare(strict_types=1);
namespace alert;

use weather\WeatherForecast;

interface ObserverInterface
{
  public function updateForecast(WeatherForecast $weaterForecast): void;
}

Plik InternetNews.php

<?php
declare(strict_types=1);
namespace alert;

use alert\ObserverInterface;
use weather\WeatherForecast;

class InternetNews implements ObserverInterface
{
  public function updateForecast(WeatherForecast $weaterForecast): void
  {
    echo "Internet - nowa prognoza pogody: ";
    echo "temperatura: " . $weaterForecast->getTemperature()  . " stopni, ";
    echo "ciśnienie: " . $weaterForecast->getPressure() . " hPa <br />";
  }
}

Plik RadioNews.php

<?php
declare(strict_types=1);
namespace alert;

use alert\ObserverInterface;
use weather\WeatherForecast;

class RadioNews implements ObserverInterface
{
  public function updateForecast(WeatherForecast $weaterForecast): void
  {
    echo "Radio - nowa prognoza pogody: ";
    echo "temperatura: " . $weaterForecast->getTemperature()  . " stopni, ";
    echo "ciśnienie: " . $weaterForecast->getPressure() . " hPa <br />";
  }
}

Plik TvNews.php

<?php
declare(strict_types=1);
namespace alert;

use alert\ObserverInterface;
use weather\WeatherForecast;

class TvNews implements ObserverInterface
{
  public function updateForecast(WeatherForecast $weaterForecast): void
  {
    echo "Telewizja - nowa prognoza pogody: ";
    echo "temperatura: " . $weaterForecast->getTemperature()  . " stopni, ";
    echo "ciśnienie: " . $weaterForecast->getPressure() . " hPa <br />";
  }
}

Plik ObservableInterface.php

<?php
declare(strict_types=1);
namespace weather;

use alert\ObserverInterface;

interface ObservableInterface
{
  public function registerObserver(ObserverInterface $observerInterface): void;
  public function unregisterObserver(ObserverInterface $observerInterface): void;
  public function notifyObservers(): void;
}

Plik WeatherForecast.php

<?php
declare(strict_types=1);
namespace weather;

use weather\ObservableInterface;
use alert\ObserverInterface;

class WeatherForecast implements ObservableInterface
{
  private int $temparature;
  private int $pressure;
  private array $registeredObservers = [];

  public function __construct(int $temparature, int $pressure)
  {
    $this->temparature = $temparature;
    $this->pressure = $pressure;
  }

  public function setTemperature(int $temperature): void
  {
    $this->temparature = $temperature;
  }

  public function setPressure(int $pressure): void
  {
    $this->pressure = $pressure;
  }

  public function getTemperature(): int
  {
    return $this->temparature;
  }

  public function getPressure(): int
  {
    return $this->pressure;
  }

  public function registerObserver(ObserverInterface $observerInterface): void
  {
    array_push($this->registeredObservers, $observerInterface);
  }

  public function unregisterObserver(ObserverInterface $observerInterface): void
  {
    foreach ($this->registeredObservers as $key => $observer) {
      if ($observer === $observerInterface) {
        unset($this->registeredObservers[$key]);
      }
    }
  }

  public function notifyObservers(): void
  {
    foreach ($this->registeredObservers as $observer) {
      $observer->updateForecast($this);
    }
  }

  public function updateForecast(int $temperature, int $pressure)
  {
    $this->setTemperature($temperature);
    $this->setPressure($pressure);
    $this->notifyObservers();
  }
}

Plik index.php

<?php
declare(strict_types=1);

require_once __DIR__ . "/alert/ObserverInterface.php";
require_once __DIR__ . "/alert/InternetNews.php";
require_once __DIR__ . "/alert/RadioNews.php";
require_once __DIR__ . "/alert/TvNews.php";
require_once __DIR__ . "/weather/ObservableInterface.php";
require_once __DIR__ . "/weather/WeatherForecast.php";

use alert\{InternetNews, RadioNews, TvNews};
use weather\WeatherForecast;

$weaterForecast = new WeatherForecast(22, 1013);

$internetNews = new InternetNews();
$radioNews = new RadioNews();
$tvNews = new TvNews();

$weaterForecast->registerObserver($internetNews);
$weaterForecast->registerObserver($radioNews);
$weaterForecast->registerObserver($tvNews);

$weaterForecast->notifyObservers();

$weaterForecast->unregisterObserver($radioNews);
$weaterForecast->unregisterObserver($tvNews);

echo "-----------------------------------------------------------------------------" . "<br />";
echo "Nowa prognoza - powiadomienie tylko dla serwisu internetowego:" .  "<br />";

$weaterForecast->updateForecast(25, 1000);

Wynik działania:

Internet - nowa prognoza pogody: temperatura: 22 stopni, ciśnienie: 1013 hPa
Radio - nowa prognoza pogody: temperatura: 22 stopni, ciśnienie: 1013 hPa
Telewizja - nowa prognoza pogody: temperatura: 22 stopni, ciśnienie: 1013 hPa
-----------------------------------------------------------------------------
Nowa prognoza - powiadomienie tylko dla serwisu internetowego:
Internet - nowa prognoza pogody: temperatura: 25 stopni, ciśnienie: 1000 hPa

Podsumowanie

Na powyższym przykładzie najpierw dodano obserwatorów do podmiotu, obserwatorzy zostali powiadomieni o prognozie pogody, a następnie pozostawiono tylko jednego obserwatora, który otrzymał prognozę po raz drugi. Klient ma możliwość dodawania i usuwania obiektów obserwujących podmiot, dlatego liczba obserwujących nie musi być znana na samym początku. Co więcej można dodawać do aplikacji nowe klasy implementujące interfejs ObserverInterface bez konieczności modyfikacji istniejącego kodu – spełniamy wtedy Zasadę otwarte-zamknięte (2 zasada SOLID).

Scroll to Top