Az előző bejegyzésekben a megfigyelő tervezési mintán (observer design pattern) alapuló alkalmazási példákkal és azok megvalósítási változataival foglalkoztunk. Ezek a klasszikus implementáción alapultak. Ez azt jelenti, hogy a publikáló és feliratkozó, vagy más szóval a megfigyelt és a megfigyelő konkrét osztályok a Publisher, illetve a Subscriber osztályokból örökölték a megfigyelő minta alkalmazásához szükséges képességeket, mint például a feliratkozók nyilvántartásba vétele vagy onnan törlése, és a feliratkozók értesítése a megfigyelt osztályban, illetve az értesítések fogadása a megfigyelő osztályban.
E képességeket a konkrét megfigyelt és megfigyelő osztályok számára más módon is lehet biztosítani. Például az osztályobjektumok dekorálásával. Ebben a bejegyzésben ennek megvalósítását mutatjuk be.
Alább egy megfigyelt (Observable) és egy megfigyelő (Observer) osztály definícióját láthatjuk. Ezek a könnyű értelmezhetőség kedvvért nagyon egyszerűek, csak a minta működéséhez és a tesztelhetőséghez szükséges tartalommal rendelkeznek.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@publisher class Observable: def __init__(self, name, x): self.name = name self.x = x def set_x(self, x): self.x = x self.notify_subscribers(self.name, x) @subscriber class Observer: def __init__(self, name): self.name = name def __repr__(self): return '{}()'.format(type(self).__name__) def update(self, subject_name, subject_value): print(f'{self.name}: a {subject_name} új értéke:', subject_value) |
Az Observable lényege itt, hogy állapota megváltozásakor értesítést küldjön a notify_subscribers() metódus meghívásával. Ez a metódus láthatólag nincs definiálva az osztályban, és nem is örökli egy szülőosztálytól. Ezt, valamint a feliratkozók nyilvántartásba vételére vagy onnan való törlésre szolgáló metódusokat a publisher nevű osztálydekorátor biztosítja.
Ami az Observer osztályt illeti, itt definiált az értesítések fogadására szolgáló update() metódus. A subscriber nevű osztálydekorátor most arra szolgál, hogy figyelmeztessen, ha nem lenne az update() definiálva az Observer osztályban, mert az előbb említett notify_subscribers() metódus ezt fogja meghívni.
A publisher és subscriber dekorátorfüggvények definíciói követhetők alább. Mindkettő egy osztályobjektumot fogad, amelyet aztán felruház a szükséges metódusattribútumokkal. Egyik szerkezete sem bonyolult, és a működés megértését a részletes kommentek segítik.
|
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 49 50 51 52 53 54 55 |
def subscriber(cls): """Dekorátor, amellyel a megfigyelő objektumok osztályát kell dekorálni.""" def _update(self, *args, **kwargs): raise NotImplementedError('Az "update" metódus nem implementált.') # Ellenőrizzük, hogy az argumentumként kapott osztály implementálta-e az update nevű metódust. # Ezt úgy tesszük, hogy megvizsgáljuk, hogy az update attribútum hívható-e. Ha igen, akkor nincs teendő. # Ha viszont nincs update attribútumnév, akkor kivétel fog keletkezni. Ekkor a kivételkezelő kódban az # osztályobjektumhoz társítunk egy update attribútumot a fenti _update függvényobjektumot mint értéket hozzárendelve. # Az így létrejövő metódust kell a cls osztályban majd felülírni. Ha ez nem történik meg, akkor az _update # NotImplementedError kivételdobása fogja ezt jelezni. try: callable(cls.update) except AttributeError: cls.update = _update return cls def publisher(cls): """Dekorátor, amellyel a megfigyelt objektum osztályát kell dekorálni.""" def _add_subscriber(self, subscriber): """Az argumentumként megadott feliratkozót felveszi a nyilvántartásba.""" # Megpróbáljuk hozzáadni a megfigyelt objektum példányához (self) társított # konténerhez (jelen esetben halmazhoz) az új feliratkozót. Ha ez ez első, akkor még nincs ilyen konténere a # példánynak és kivétel keletkezik. Ennek lekezelő ágában hozzáadjuk a megfigyelt objektum példányához a # feliratkozókat (megfigyelőket) tároló konténert, és ezt követően adjuk hozzá az új feliratkozót. try: self._subscribers.add(subscriber) except AttributeError: self._subscribers = set() self._subscribers.add(subscriber) def _remove_subscriber(self, subscriber): """Az argumentumként megadott feliratkozót törli a nyilvántartásból.""" self._subscribers.discard(subscriber) def _notify_subscribers(self, *args, **kwargs): """Minden feliratkozó értesítést kap, amelyben átadásra kerül a változásssal érintett érték.""" for subscriber in self._subscribers: subscriber.update(*args, **kwargs) # Az argumentumként kapott osztályobjektumban létrehozzuk a metódusokat a fenti függvényobjektumokat # mint értékeket hozzájuk rendelve. cls.add_subscriber = _add_subscriber cls.remove_subscriber = _remove_subscriber cls.notify_subscribers = _notify_subscribers return cls |
A következő kódsorokban a két osztály használatát, illetve a helyes működés tesztelését láthatjuk. A kommentek itt is segítenek követni, hogy mikor mi történik.
|
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 |
# TESZT # Létrehozunk két megfigyelt objektumot és két megfigyelőt. subjects = [Observable('Megfigyelt1', 123), Observable('Megfigyelt2', 456)] observers = [Observer('Feliratkozó1'), Observer('Feliratkozó2')] # Mindkét feliratkozót beregisztráljuk mindkét megfigyelt objektumhoz. for observer in observers: for subject in subjects: subject.add_subscriber(observer) # Mindkét megfigyelt objektum állapota változik, amit mindkét feliratkozó érzékel. subjects[0].set_x(333) subjects[1].set_x(999) # Az egyik feliratkozót eltávolítjuk az egyik megfigyelt objektum értesítési nyilvántartásából. subjects[0].remove_subscriber(observers[0]) # Mindkét megfigyelt objektum állapota változik, de most már csak egy feliratkozó szerez tudomást mindkét változásról. subjects[0].set_x(0) subjects[1].set_x(-1) # Eredmények: # Feliratkozó2: a Megfigyelt1 új értéke: 333 # Feliratkozó1: a Megfigyelt1 új értéke: 333 # Feliratkozó2: a Megfigyelt2 új értéke: 999 # Feliratkozó1: a Megfigyelt2 új értéke: 999 # Feliratkozó2: a Megfigyelt1 új értéke: 0 # Feliratkozó2: a Megfigyelt2 új értéke: -1 # Feliratkozó1: a Megfigyelt2 új értéke: -1 |
E bejegyzés fókuszában a dekorátorok állnak, ezért a fentiekhez kapcsolódóan a Python tudásépítés lépésről lépésre című e-könyvben az „Osztály vigyázz! – típuslétrehozás osztályokkal” fejezeten felül elsősorban a „Képességfejlesztés – függvénydekorátorok”, valamint az „Osztályok dekorálása” című fejezeteket érdemes átnézni.