Tacsiazuma
Tacsiazuma A letscode.hu alapitója, több, mint egy évtized fejlesztői tapasztalattal. Neovim függő hobbi pilóta.

Observer pattern és a Robotics Facility

![starcraft_2_observer_by_worthart-d4ibdiy](assets/uploads/2015/06/starcraft_2_observer_by_worthart-d4ibdiy.jpg)
Protoss observer - Ha nem ismerős, akkor annak idején Te is lemaradtál a dolgokról.. Ez a pattern talán segíthet, hogy ne történjen meg újra!

Akit kapott már hátba egy hadseregnyi zergling, az pontosan tudja, hogy jó dolog, ha időben értesülünk a dolgokról körülöttünk. Az objektumokkal sincs ez másként, bizony őket sem érinti pozitívan, ha bezergelik őket lemaradnak a számukra fontos eseményekről.

A mostani témánk az observer pattern lesz, ami a viselkedési minták egyike. Nem, nem a kémkedésről szól, a működése leginkább a hírlevelek világához kötődik, de nem kell megijedni, csak a valóságközeli példák miatt hoztam fel.

Tegyük fel, hogy szeretnénk hírlevelet értesítőt küldeni a felhasználóinknak, amikor egy adott adatbázis tábla módosul. Az ilyen és hasonló feladatokra találták ki az observer pattern-t, aminek a lényege, hogy az A objektum “feliratkozik” bizonyos eseményekre, ami események bekövetkeztekor értesítve lesz, vagyis meghívunk rajta egy metódust. De lássuk ezt valami csontszáraz példán át:

Először is hozzunk létre egy feliratkozót, sőt… először egy interfészt, amit a feliratkozónak implementálnia kell. Később meglátjuk mire is jó mindez:

1
2
3
4
5
interface DatabaseEventAwareInterface { // ami ezt az interfészt implementálja, azon meghívhatóak lesznek az alábbi függvények, tehát a programunk nem fog hibára futni, ha megpróbálja azt
     public function onUpdate(Record $record); // az adatbázis update-kor meghívódó metódus
     public function onInsert(Record $record); // beszúráskor hívódik meg
     public function onDelete(Record $record); // törléskor hívódik meg
}

Most, hogy kész az interfész, nem ártana egy osztályt is kreálni, amivel megvalósítjuk:

1
2
3
4
5
6
7
8
9
10
11
abstract class DatabaseObserver implements DatabaseEventAwareInterface {
     public function onUpdate(Record $record) {
      // override-ra vár
     }
     public function onInsert(Record $record) {
      // override-ra vár
     }
     public function onDelete(Record $record) {
     // override-ra vár
     }
}

Na jó, hazudtam, ez még nem a megvalósítás volt…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class OurFancyObserver extends DatabaseObserver { // leszármaztatjuk, ennek fényében megkapjuk a szép üres metódusokat fent, ahhoz, hogy valamit érjünk is vele, override-olni kell azt
    public function onUpdate(Record $record) {
    echo "Updated ID: ".$record->getId().PHP_EOL; // A Record objektumnak legyen ez esetben egy public getter-e az ID mezőre.
    }

    public function onDelete(Record $record) {
    echo "Deleted ID: ". $record->getId().PHP_EOL;
    }
    

    public function onInsert(Record $record) {
    echo "Inserted ID: ". $record->getId().PHP_EOL;
    }
}

Bumm, ennyi volt az observer.. viszont ez még édeskevés. Most csak leírtuk milyen is külsőre egy átlag feliratkozó, nem pipálja ki a toolbar-okat és IE-t használ viszont valamire fel is kéne iratkozni.

Hozzuk létre hát az adatbázis osztályunkat eseménykezelő interfészt:

1
2
3
interface EventManagerInterface { 
      public function attach(DatabaseAwareInterface $observer); // az observer-eink a typehintelt interfészt fogják implementálni, így nem fut cs*csre a kódunk, az átadáskor. Ezzel a metódussal tudjuk majd ráakasztani az observerünket az eseménykezelőre, ami az átadott példányon meghívja az egyes metódusokat amikor az adott esemény lezajlik
}

Jöjjön akkor ismét a megvalósítás, eléggé lebutítva persze:

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
class DatabaseEventAndStuff implements EventManagerInterface {

     private $observers = array(); // a feliratkozókat tartalmazó tömb.     

     public function attach(DatabaseAwareInterface $observer) {
           array_push($observer, $this->observers); // feltoljuk a listára az observerünket. Persze ez a módszer nem a legjobb, mivel egy tömbből kikapni 1 index-et ilyen formában nem túl egyszerű, de pl. visszaadhatjuk az index-ét ezen metódusban, stb.
     }
     public function insert(Record $record) {
           // valami adatbázis logika
           foreach ($this->observers as $observer) { // a konkrét értesítés

                $observer->onInsert($record); // itt értesítjük az egyes observer-eket.
           }
     }

}


// menjenek tovább, nincs itt semmi látnivaló! Csak a hitelesség kedvéért implementáljuk a fent említett getId-t és a Record osztályt.
class Record {

    private $id;

    public function __construct($id) {
         $this->id = $id;
    }

    public function getId() {
          return $this->id;
    }

}

Végül pedig használjuk az Erőt, vagy elég azt a pár osztályt is, amit csináltunk.

1
2
3
4
5
6
7
8
$db = new DatabaseEventAndStuff(); // példányosítunk egy jól szituált adatbázis osztályt
$db->attach(new OurFancyObserver()); // ráakasztunk az adatbázisra egy observer-t
$db->insert(new Record(1)); // végül beillesztünk egy rekord-ot.
// Inserted ID: 1
$db->delete(new Record(2)); 
// Deleted ID: 2
$db->update(new Record(3));
// Updated ID: 3

Amint láthatjuk a dolog lényege annyi, hogy az adott eseménykezelőre feliratkoztatunk egy megfigyelőt (átadunk neki egy referenciát az observer osztályra), ami az adott események esetén meghív az adott osztályon egy metódust.
A problémát ezen minta esetében a memória jelenti. Ugyanis amikor feliratkoztatunk egy osztályt, akkor ún. strong reference-ként adjuk át az adott osztályt, ami azt jelenti, hogy a garbage collection nem tudja kitakarítani azt a memóriából. Ezt elkerülendő weak reference-ként szokás átadni az adott példányt. Ez PHP-ben nem elérhető, viszont itt nem is gyakori, hogy memory leak-el küdeznénk, lévén nem sok hosszan futó process-t írnak a nyelvben (Kivéve a hozzám hasonló elvetemültek).

Az observer pattern az MVC architektúrának képezi egyik fontos építőelemét. Ha esetleg érdekli a népet, akkor ennek megvalósításáról is írok legközelebb.

comments powered by Disqus