• Nem Talált Eredményt

Megfigyelő – Observer

In document Programozás technika (Pldal 118-123)

4. Programozási technológiák – Tervezési minták

4.4. Viselkedési tervezési minták

4.4.2. Megfigyelő – Observer

Lehetővé teszi, hogy egy objektum megváltozása esetén értesíteni tudjon tetszőleges más objektumokat anélkül, hogy bármit is tudna róluk. Részei:

1. Alany: Tárolja a beregisztrált megfigyelőket, interfészt kínál a megfigyelők be- és kiregisztrálására valamint értesítésére.

2. Megfigyelő: Interfészt definiál azon objektumok számára, amelyek értesülni szeretnének az alanyban bekövetkezett változásról. Erre a frissít (update) metódus szolgál.

Két fajta megfigyelő megvalósítást ismerünk:

1. „Pull-os” megfigyelő: Ebben az esetben a megfigyelő lehúzza a változásokat az alanytól.

2. „Push-os” megfigyelő: Ebben az esetben az alany odanyomja a változásokat a megfigyelőnek.

A kettő között ott van a különbség, hogy a frissít metódus milyen paramétert kap. Ha az alany átadja önmagát (egy frissít(this) hívás segítségével) a megfigyelőnek, akkor ezen a referencián keresztül a megfigyelő képes lekérdezni a változásokat. Azaz ez a „pull-os” megoldás.

Ha a frissít metódusnak az alany azokat a mezőit adja át, amik megváltoztak és amiket a megfigyelő figyel, akkor „push-os” megoldásról beszélünk. A következő példában épp egy ilyen megvalósítást láthatunk.

4.4.2.1. Forráskód using System;

using System.Collections.Generic;

namespace Observer_Pattern_Weather_Station {

public interface ISubject {

// observer regisztrálásra

void registerObserver(IObserver o);

// observer törlésre

void removeObserver(IObserver o);

// meghívódik, hogy értesítse az megfigyelőket // amikor a Subject állapota megváltozik void notifyObservers();

}

public interface IObserver {

// értékék amiket megkapnak az observerek a Subjecttől, push-os megoldás void update(float temp, float humidity, float pressure);

}

public interface IDisplayElement {

// meghívjuk amikor meg szeretnénk jeleníteni a display elementet void display();

}

// implementáljuk a Subject interfészt public class WeatherData : ISubject {

// hozzáadunk egy listát amiben observereket tárolunk private List<IObserver> observers;

private float temperature;

private float humidity;

private float pressure;

public WeatherData() {

// létrehozzuk az observereket tároló listát observers = new List<IObserver>();

}

public void registerObserver(IObserver o) {

// amikor egy observer regisztrál, egyszerűen hozzáadjuk a listához observers.Add(o);

}

public void removeObserver(IObserver o) {

// amikor egy observer le akar regisztrálni egyszerűen töröljük a listából int i = observers.IndexOf(o);

if (i >= 0) {

observers.Remove(o);

}

}

// itt szólunk az observereknek az állapotról

// mivel mind observerek, van update() metódusuk, így tudjuk őket értesíteni public void notifyObservers()

{

for (int i = 0; i < observers.Count; i++) {

IObserver observer = (IObserver)observers.ElementAt(i);

observer.update(temperature, humidity, pressure); // ez push-os // observer.update(this); // ez pull-os

} }

// amikor a Weather Station-től megkapjuk a frissített értékeket, //értesítjük az observereket

public void measurementsChanged() {

notifyObservers();

}

// értékek beállítása hogy tesztelhessük a display elementeket

public void setMeasurements(float temperature, float humidity, float pressure) {

this.temperature = temperature;

this.humidity = humidity;

this.pressure = pressure;

measurementsChanged();

}

// egyéb metódusok }

// a display implementálja az Observert,

//így fogadhat változásokat a WeatherData objektumtól // továbbá implementálja a DisplayElement-et, mivel

//minden display element-nek implementálnia kell ezt az interfészt

public class CurrentConditionsDisplay : IObserver, IDisplayElement {

private float temperature;

private float humidity;

private ISubject weatherData;

// a konstruktor megkapja a weatherData objektumot // (a Subject) és arra használjuk, hogy

// a display-t observerként regisztráljuk

public CurrentConditionsDisplay(ISubject weatherData) {

this.weatherData = weatherData;

weatherData.registerObserver(this);

}

// amikor az update() meghívódik, mentjük a temperature-t és a humidity-t // majd meghívjuk a display()-t

public void update(float temperature, float humidity, float pressure) {

this.temperature = temperature;

this.humidity = humidity;

display();

}

// Megjelenítjük a legújabb eredményeket public void display()

{

Console.WriteLine("Current conditions: " + temperature + "F degrees and " + humidity + "%

humidity");

} }

public class WeatherStation {

static void Main(string[] args) {

// létrehozzuk a weatherData objektumot

WeatherData weatherData = new WeatherData();

// létrehozzuk a displayt és odaajuk neki a weatherData-t

CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

// új időjárási mérésértékek szimulálása weatherData.setMeasurements(80, 65, 30.4f);

weatherData.setMeasurements(82, 70, 29.2f);

weatherData.setMeasurements(78, 90, 29.2f);

Console.ReadKey();

} } }

4.4.2.2. UML-ábra

23. ábra

4.4.2.3. Gyakorló feladat

Az alábbi leírás szerint készítsünk forráskódot. A megoldáshoz használjuk a megfigyelő tervezési mintát!

Feladat

A fenti példakódot alakítsuk át „pull-os” megfigyelővé.

Feladat

Cégünk azt a megtisztelő feladatot kapta, hogy kalózhajót kellett programozni, ami több heti kódírás után egész szépen úszott a vízen, egy picike probléma volt csak vele. Mégpedig, hogy a főárboc tetején lévő kosárban ülő őrszemnek nem volt rádiója, hiszen még nem találták fel, ezért minden alkalommal, ha valaki alatta ment el az felkiabált, hogy látja-e már a gazdag zsákmányt, vagy az ellent. Szegény emberünknek úgy kiszáradt a torka, hogy egy hordó rumot kellett délig meginnia. A probléma orvoslására és a rumkészlet megmentésére azt találtuk ki, hogy aki szeretne értesülni a hírekről (observer) az köt egy kötelet a csuklójához, majd a másik végét feldobja (registerObserver()) az őrszemnek (subject). Amikor az őrszem említésre méltót lát, akkor megrángatja a kötelek végét (notifyObservers()), és az összegyűlt „megfigyelőknek” lekiabálja a hírt (update()). Aki nem sablonfüggvény mintát gyakran hasonlítják össze a stratégia mintával az alábbi mondattal:

1. Stratégia: Ugyanazt csináljuk, de másképp;

2. Sablonfüggvény: Ugyanúgy csináljuk, de mást.

A receptben háromféle lépés lehet:

1. kötelező és közös: bármit készítünk a recepttel ez a lépés mindig ugyanaz,

2. kötelező, de nem közös: bármit készítünk a recepttel ez a lépés szükséges, de minden esetben mást és mást kell konkrétan csinálni,

3. opcionális: ez a lépés nem minden esetben szükséges.

Ezeket programozás technikailag így valósíthatjuk meg:

1. A kötelező és közös lépések olyan metódusok, amelyek már az ősben konkrétak és azokat általában nem is szabad felülírni. Ilyen a forró ital főzésénél a vízforraló metódus.

2. A kötelező, de nem közös lépések az ősben absztrakt metódusok, amit a gyermek osztályok fejtenek ki. Ilyen a forró ital főzésénél az édesítés.

3. Az opcionális lépések az ősben hook metódusok, azaz van törzsük, de az üres.

Mivel a gyermek osztálynak implementálnia kell minden absztrakt metódust, ezért az ilyenek kötelezőek. Igaz, hogy akár az implementáció üres is lehet. Mivel a hook metódusoknak van implementációjuk, de üres az üres, ezért nem muszáj őket felülírni, de lehet az OCP elv megszegése nélkül. Ezért ezek az opcionális lépések. A hook metódusokat C# nyelven virtuálisnak kell deklarálni.

Maga a recept a sablonfüggvény. Gyakran csak ez az egy metódus publikus, minden más metódus, azaz a recept lépései privát vagy védett metódusok (a szerint, hogy a gyermeknek felül írhatja-e vagy sem). Erre azért van szükség, hogy az egyes lépéseket ne lehessen össze-vissza sorrendben hívni, csak a recept által rögzített sorrendben.

Elméletben a sablonfüggvény egy algoritmus, amelyben a lépések nem változnak, de a lépések tartalma igen. Ha esetleg mégis bejön egy új lépés, azt érdemes hook metódusnak felvenni.

Érdekes megfigyelni, hogy az absztrakt ős és a gyermek osztályai IOC (inversion of control) viszonyban állnak hasonlóan, mint a gyártófüggvény esetén. Ugyanúgy itt is nem a gyermek hívja az ős metódusait, hanem az ős a gyermekét. Ezt úgy érjük el, hogy a sablonfüggvény absztrakt, illetve virtuális metódusokat hív. Amikor a gyermek osztály példányán keresztül hívjuk majd a sablonfüggvényt, akkor a késői kötés miatt ezen metódusok helyett az őket felülíró gyermek béli metódusok fognak lefutni.

In document Programozás technika (Pldal 118-123)