Wzorzec Obserwatora [Cpp, Java, PHP]

Cześć! Cieszę się, że mnie odwiedziłeś/aś. Zanim przejdziesz do artykułu chciałbym zwrocić Ci uwagę na to, że ten artykuł był pisany kilka lat temu (2013-12-22) miej więc proszę na uwadzę że rozwiązania i przemyślenia które tu znajdziesz nie muszą być aktualne. Niemniej jednak zachęcam do przeczytania.

Korzystając z faktu iż mamy przerwę świąteczną postanowiłem znaleźć trochę czasu na naskrobanie tego artykułu i napisanie przykładów w 3  językach – PHP, C++ i Java. Patrzenie na implementacje tej samej rzeczy w różnych językach pozwala łatwo przyswoić sobie informacje, co jaki język potrafi „sam od siebie”.

Ale wracając do tematu – wzorce projektowe to rozwiązania częstych problemów, które pojawiają się podczas pisania kodu. Formalnie dzielimy je na grupy – grupujemy wg. przeznaczenia. Wszystkie wzorce, pogrupowane to:

Wzorce kreacyjne:

  • Budowniczy (obiektowy),
  • Fabryka abstrakcyjna (obiektowy),
  • Metoda wytwórcza (klasowy),
  • Prototyp (obiektowy),
  • Singleton (obiektowy);

Wzorce strukturalne:

  • Adapter (klasowy oraz obiektowy),
  • Dekorator (obiektowy),
  • Fasada (obiektowy),
  • Kompozyt (obiektowy),
  • Most (obiektowy),
  • Pełnomocnik (obiektowy),
  • Pyłek (obiektowy);

Wzorce czynnościowe (behawioralne):

  • Interpreter (klasowy),
  • Iterator (obiektowy),
  • Łańcuch zobowiązań (obiektowy),
  • Mediator (obiektowy),
  • Metoda szablonowa (klasowy),
  • Obserwator (obiektowy),
  • Odwiedzający (obiektowy),
  • Pamiątka (obiektowy),
  • Polecenie (obiektowy),
  • Stan (obiektowy),
  • Strategia (obiektowy),
  • Zabór Zasobu Jest Inicjalizacją (obiektowy).

Listę zaczerpnąłem z wikipedii. Pogrubiłem interesujący nas wzorzec – Obserwatora.

Jak napisałem wcześniej, wzorce rozwiązują problemy jakie można napotkać podczas pisania kodu.

Obserwator rozwiązuje problem, który pojawia się gdy zmiana stanu jednego z obiektów ma wywołać zmianę w innym obiekcie. Przez zmianę stanu rozumiemy wywołanie jakiejś metody np :) chociażby gettera czy settera.

Problem jest bardzo powszechny, gdyż cała idea programowania obiektowego opiera się właśnie o elastyczność w komunikacji obiektów.

Rozwiązanie problemu sprowadza się do dwóch interfejsów:

  • Interfejs Obserwowanego
  • Interfejs Obserwującego

Ten pierwszy (u mnie w przykładach nazwany Observable, czyli dający się obserwować) wymusza implementację metod:

  • addObservator(Observator o)
  • removeObservator(Observator o)
  • notifyObservators()

Ten drugi (u mnie nazwany Observator, czyli obserwujący) wymusza implementację:

  • update()

 

Schemat działania jest prosty

Mamy obiekt A i obiekt B. Obiekt A implementuje Observable, obiekt B Observator. Obiekt A jest teraz obiektem dającym się obserwować, więc ma metodę addObservator(Observator o), używając jej dodajemy obiekt B. I zmiana obiektu A będzie powiadamiać obiekt B.

Oczywiście moja implementacja jest tylko jedną z implementacji. Można ją modyfikować i customize’ować do własnych potrzeb. Dyskutować można chociażby o tym czy metoda nofity powinna być wywoływana w klasie czy przez użytkownika klasy. Metoda update() jest bezparametrowa, może można by to zmienić.. :)

Tak czy siak, przyjrzyjmy się implementacji. Mi najłatwiej przyszło zaimplementować to w Javie – interfejsy, listy, to wszystko mam na zawołanie.

Interfejs Observable:

1
2
3
4
5
6
7
8
public interface Observable {

  public void addObservator(Observator object);

  public void deleteObservator(Observator object);

  public void notifyObservator();
}

Oraz interfejs Observator:

1
2
3
4
5
public interface Observator {

  public void update();

}

 

W naszym przykładzie użyjemy sytuacji codziennej :) jak wiadomo Kobiety interesują się mężczyznami i odwrotnie. Więc zaimplementujmy klasę Kobieta i klasę Mezczyzna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import java.util.ArrayList;
public class Mezczyzna implements Observator, Observable {
  String name;
  private ArrayList<Observator> observators= new ArrayList<Observator>();

  public Mezczyzna(String name) {
      this.name=name;
  }
  @Override
  public void update() {

      System.out.println(name+" wlasnie zauwazyl, ze Kobieta zmienila spodnie na spodniczke.");

  }
  @Override
  public void addObservator(Observator object) {
      if(object!=null)
          observators.add(object);

  }
  @Override
  public void deleteObservator(Observator object) {
      this.observators.remove(object);

  }
  @Override
  public void notifyObservator() {

  for(Observator ob:observators)
      ob.update();

  }

  public void changeBlouseToShirt(){

  System.out.println(name+" zmienia bluzke na koszule.");
  notifyObservator();
  }

}

no i klasa Kobieta:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import java.util.ArrayList;
public class Kobieta implements Observable, Observator {

  private String name;
  private ArrayList<Observator> observators= new ArrayList<Observator>();

  public Kobieta(String name) {
      this.name=name;
  }

  public void changeTrousersToSkirt(){

      System.out.println(name+" zmienia spodnie na spodniczke :) ");
      notifyObservator();

  }

  @Override
  public void deleteObservator(Observator object) {

      observators.remove(object);

  }

  @Override
  public void addObservator(Observator object) {

  if(object!=null)
      observators.add(object);

  }

  @Override
  public void notifyObservator() {

  for(Observator ob:observators)
      ob.update();

  }

  @Override
  public void update() {

      System.out.println(name+" zauwaza ze ktos zmienil bluzke na koszule, mrr.. :)");

  }

}

Przeanalizuj kod :)

Użyjmy tych klas:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {

public static void main(String[] args) {

Kobieta ob1 = new Kobieta("Aga");
Mezczyzna ob2 = new Mezczyzna("Mateusz");
Mezczyzna ob3 = new Mezczyzna("Lukasz");
//========================
ob1.addObservator(ob2);
ob1.addObservator(ob3);
ob1.changeTrousersToSkirt();
//========================
ob2.addObservator(ob1);
ob2.changeBlouseToShirt();

}

}

Tworzymy więc obiekt Kobiety o imieniu Aga, oraz dwóch mężczyzn o imionach Mateusz i Łukasz. Dodajemy mężczyzn jako obserwatorów Agnieszki. Agnieszka zmienia spodnie na spódniczkę i od razu idzie powiadomienie do obiektów Mezczyzn, ktorzy potwierdzają odebranie wiadomości poprzez wypisanie na ekranie odpowiedniego komunikatu.

Potem robimy odwrotnie. Mateusz zaczyna obserwować Agnieszka :) Następnie Mateusz zmienia bluzkę na koszulę, co rzecz jasna, spotyka się z natychmiastową reakcją Agnieszki.

Wynik tego kodu to:

Aga zmienia spodnie na spodniczke :)
Mateusz wlasnie zauwazyl, ze Kobieta zmienila spodnie na spodniczke.
Lukasz wlasnie zauwazyl, ze Kobieta zmienila spodnie na spodniczke.
Mateusz zmienia bluzke na koszule.
Aga zauwaza ze ktos zmienil bluzke na koszule, mrr.. :)

Teraz to samo zróbmy w C++. Nie będzie to już tak proste – gdyż w C++ nie ma interfejsów. W ich miejsce wykorzystamy klasy abstrakcyjne (pure abstract).

Ok, więc własnie od nich:

1
2
3
4
5
6
7
8
9
10
11
#ifndef OBSERVATOR_H
#define OBSERVATOR_H
class Observator
{
public:
  virtual void update() =0;
protected:
private:
};

#endif // OBSERVATOR_H

oraz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef OBSERVABLE_H
#define OBSERVABLE_H
#include "observator.h"

class Observable
{
public:
  virtual void addObservator(Observator &object)=0;
  virtual void removeObservator(Observator &object)=0;
protected:
  virtual void notifyObservators()=0;
private:
};

#endif // OBSERVABLE_H

W C++ nie ma słówka kluczowego implements, ale można za to dziedziczyć po wielu klasach, tę właściwość wykorzystamy przy implementowaniu zachować dla Kobiety i Mężczyzny.

A więc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef KOBIETA_H
#define KOBIETA_H
#include "observable.h"
#include "observator.h"
#include <list>
#include <string>

class Kobieta : public Observable, public Observator
{
public:
  Kobieta(std::string name);
  virtual ~Kobieta();
  void changeTrousersToSkirt();
  void addObservator(Observator &object);
  void removeObservator(Observator &object);
  void update();
protected:
  void notifyObservators();
private:
  std::string name;
  std::list<Observator*> observators;
};

#endif // KOBIETA_H

Nie możemy stworzyć instancji klasy Observator, ale możemy mieć wskaźnik wskazujący na ten typ, co wykorzystujemy w typie listy.

Klasa Mezczyzna wygląda bardzo podobnie:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef MEZCZYZNA_H
#define MEZCZYZNA_H

#include "observator.h"
#include "observable.h"
#include <string>
#include <list>

class Mezczyzna : public Observator, public Observable
{
public:
  Mezczyzna(std::string name);
  virtual ~Mezczyzna();
  void changeBlouseToShirt();
  void addObservator(Observator &object);
  void removeObservator(Observator &object);
protected:
  void notifyObservators();
private:
  void update();
  std::string name;
  std::list<Observator*> observators;
};

#endif // MEZCZYZNA_H

Pamiętajcie że to dopiero pliki nagłówkowe. Rozdziela się strukturę klasy od jej implementacji. Struktura to pliki *.h, pliki źródłowe to *.cpp. Teraz spójrzmy na pliki źródłowe..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "../include/kobieta.h"
#include <list>
#include <iostream>
using namespace std;
Kobieta::Kobieta(string name)
{
  this->name=name;
}
Kobieta::~Kobieta()
{
  //dtor
}
void Kobieta::changeTrousersToSkirt(){

  std::cout << name<<" zmienia spodnie na spodniczke." << std::endl;
  this->notifyObservators();

}

void Kobieta::notifyObservators(){

  list<Observator*>::iterator iter;
  for(iter=observators.begin(); iter != observators.end(); iter++ )
      (*iter)->update();

}
void Kobieta::addObservator(Observator &object){

  observators.push_back(&object);

}

void Kobieta::removeObservator(Observator &object){

  observators.remove(&object);

}

void Kobieta::update(){
  cout<<"Kobietka zauwazyla, ze Mezczyzna, ktorego obserwuje zmienil bluzke na koszule :)"<<endl;
}

oraz Mezczyzna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include "../include/mezczyzna.h"
#include <iostream>
#include <string>
using namespace std;
Mezczyzna::Mezczyzna(string name)
{
  this->name=name;
}

Mezczyzna::~Mezczyzna()
{
  //nothing to fo here
}
void Mezczyzna::update(){
  cout<<name<<" zauwazyl ze Kobieta zmienila spodnie na spodniczke."<<endl;
}

void Mezczyzna::addObservator(Observator &object)
{
  observators.push_back(&object);
}

void Mezczyzna::changeBlouseToShirt(){
  cout<<name<<" zmienil bluzke na koszule."<<endl;
  this->notifyObservators();
}

void Mezczyzna::removeObservator(Observator &object){
  observators.remove(&object);
}

void Mezczyzna::notifyObservators(){
  list<Observator*>::iterator iter;
  for(iter=observators.begin(); iter != observators.end(); iter++ )
      (*iter)->update();
}

Użyłem listy z std i iterator do poruszania się po liście. Użycie referencji daje pewność że na 100% dodajemy ten obiekt który chcemy dodać (dokładnie ten adres w pamięci).

Teraz możemy sprawdzić działanie programu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include "include/kobieta.h"
#include "include/mezczyzna.h"
using namespace std;

int main()
{
Mezczyzna* ob = new Mezczyzna("Mateusz");
Mezczyzna* ob1 = new Mezczyzna("Lukasz");
Kobieta* ob2 = new Kobieta("Aga");
ob2->addObservator(dynamic_cast<Observator&>(*ob));
ob2->addObservator(dynamic_cast<Observator&>(*ob1));
ob2->changeTrousersToSkirt();
//=====================
ob->addObservator(dynamic_cast<Observator&>(*ob2));
ob->changeBlouseToShirt();

delete ob;
delete ob1;
delete ob2;
return 0;
}

Wynik działania programu to:

Aga zmienia spodnie na spodniczke.
Mateusz zauwazyl ze Kobieta zmienila spodnie na spodniczke.
Lukasz zauwazyl ze Kobieta zmienila spodnie na spodniczke.
Mateusz zmienil bluzke na koszule.
Kobietka zauwazyla, ze Mezczyzna, ktorego obserwuje zmienil bluzke na koszule :)

Czyli wszystko działa bardzo ładnie.

Teraz pokażmy jak to będzie wyglądać w PHP. Natywnie nie ma on listy, ale jest bardzo „bezproblemowym” językiem, więc dopisaliśmy sobie klasę pomocniczą, imitującą listę:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class Listunia{

private $counter;
public $buffer;
public function __construct(){
  $this->counter=0;
  $this->buffer = array();
}

public function add(Observator $object){
  $this->buffer[$this->counter]=$object;
  $this->counter++;
}

public function remove(Observator $object){
  foreach ($this->buffer as $key => $value) {
      if($value == $object)
          unset($value);
  }
}
}

Za to php mam interfejsy :) Więc, like in Java, możemy sobie je stworzyć, tak prosto, bez kombinowania:

1
2
3
4
5
6
7
<?php
interface Observable
{
  public function addObservator(Observator $object);
  public function removeObservator(Observator $object);
  public function notifyObservators();
}
1
2
3
4
5
<?php
interface Observator
{
  public function update();
}

Skoro mamy już napisane interfejsy, to zaimplementujmy je w klasach, standardowo Kobieta i Mezczyzna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
include('observable.php');
include('observator.php');
include ('list.php');

class Kobieta implements Observator, Observable{

private $observators;
private $name;

public function __construct($name){
  $this->observators = new Listunia();
  $this->name=$name;
}
public function changeTrousersToSkirt(){
  echo $this->name. " zmienia spodnie na spodniczke :)<br>";
  $this->notifyObservators();
}

public function update(){
  echo $this->name." zauwazyla ze facet, ktorego obserwuje, zmienil bluzke na koszule.<br>";
}

public function addObservator(Observator $object){
  $this->observators->add($object);
}
public function removeObservator(Observator $object){

  $this->observators.remove($object);

}
public function notifyObservators(){
  foreach ($this->observators->buffer as $key => $value) {
      $value->update();
  }
}

}

i Mezczyzna:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
class Mezczyzna implements Observator, Observable{

private $observators;
private $name;

public function __construct($name){

  $this->name=$name;
  $this->observators = new Listunia();

}
public function changeBlouseToShirt(){
  echo $this->name. " zmienia bluzke na koszule.<br>";
  $this->notifyObservators();
}

public function update(){
  echo $this->name." zauwazyl ze Kobieta zmienila spodnie na spodniczke.<br>";
}
public function addObservator(Observator $object){
  $this->observators->add($object);
}
public function removeObservator(Observator $object){

  $this->observators->remove($object);

}
public function notifyObservators(){
  foreach ($this->observators->buffer as $key => $value) {
      $value->update();
  }
}
}

Więc mamy wszystko czego potrzebujemy. Odpalamy serwer i testujemy czy działa ^^ plik index:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

include 'Kobieta.php';
include 'Mezczyzna.php';
$ob = new Kobieta("Aga");
$ob1 = new Mezczyzna("Mateusz");
$ob2 = new Mezczyzna("Lukasz");
$ob->addObservator($ob1);
$ob->addObservator($ob2);
$ob->changeTrousersToSkirt();

//=============================

$ob1->addObservator($ob);
$ob1->changeBlouseToShirt();

No i wynik, taki sam jak w innych językach..

Aga zmienia spodnie na spodniczke :)
Mateusz zauwazyl ze Kobieta zmienila spodnie na spodniczke.
Lukasz zauwazyl ze Kobieta zmienila spodnie na spodniczke.
Mateusz zmienia bluzke na koszule.
Aga zauwazyla ze facet, ktorego obserwuje, zmienil bluzke na koszule.

 

Pełne kody możecie pobrać z githuba:

Wersja w PHP

Wersja w Javie

Wersja w C++

Jeśli masz klienta Gita to polecenie git clone. Jeśli nie, to po wejściu w link na dole po prawej masz „Download Zip” :)

Wzorzec Obserwatora jest bardzo istotnym zagadnieniem dla każdego programisty, w łatwy sposób rozwiązuje problem powiadamiania obiektów o swoich zmianach.
Warto dodać że czasem wzorce są wbudowane w język. Np c# ma już podobny mechanizm zaimplementowany.

Jeśli masz sugestie, to zapraszam do forkowania repo :)

 

Dzięki za wizytę,
Mateusz Mazurek
Mateusz M.

Pokaż komentarze

  • Ogromne dzięki za tak łopatologiczne przedstawienie obserwatora. Dokładnie tego szukałem!

Ostatnie wpisy

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

2 miesiące ago

Podsumowanie: kwiecień 2024

Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More

6 miesięcy ago

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

7 miesięcy ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

9 miesięcy ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

10 miesięcy ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

11 miesięcy ago