Megfigyelő minta attribútumleírók használatával

Az előző néhány bejegyzésben a megfigyelő tervezési minta (observer design pattern) alkalmazásával és megvalósítási változataival foglalkoztunk. Láthattuk a push és pull változatok közötti elvi különbséget, majd pedig az üzenetküldés feladatát kiszerveztük egy külön objektumba. Legutóbb megnéztük, hogy öröklés helyett hogyan lehet dekorátorokkal biztosítani a publikáló és feliratkozó objektumok azon képességeit, hogy a publikáló, vagy más néven a megfigyelt objektum, állapotváltozásakor a megfelelő üzenetet el lehessen küldeni, és azt a feliratkozó vagy más néven megfigyelő objektumok fogadni tudják.

Most visszatérünk a megfigyelő tervezési minta push változatát alkalmazó korábbi áruházas példánkhoz, ahol az áruházat modellező Supermarket osztály a Publisher osztályt, a vásárlókat reprezentáló Customer osztály a Subscriber osztályt örökli. A termékeket a Product osztály, az új termék érkezésekor kiküldött értesítést (promóciós hírleveleket) a PromotionMessage osztály modellezi.

Most az értesítésküldést fogjuk az eddigiekhez képest máshogyan megvalósítani. Mégpedig attribútumleíró (attribute descriptor), vagy röviden csak leíró (descriptor) alkalmazásával.

A leíró egy olyan objektum, amely rendelkezik a __get__, __set__, __delete__ speciális metódusok közül legalább eggyel. /Ezek mellett opcionálisan még a __set_name__ metódus is definiált lehet az adott leíróra./

A leírók működésével kapcsolatos egyik fontos kitétel, hogy a bennük megvalósult __get__, __set__, __delete__ metódusok csak akkor érvényesülnek, ha az ezeket definiáló leíró osztály példánya szerepel egy osztályban. Ezt az osztályt, amely tehát a leírópéldányt tartalmazza tulajdonos (owner) osztálynak nevezik. Még pontosabban fogalmazva, a leírópéldánynak vagy a tulajdonos osztály __dict__ konténerében, vagy a tulajdonos valamely ősének __dict__ konténerében kell szerepelnie. A lényeg, hogy a leíróobjektum osztályattribútum kell, hogy legyen.

Ha így járunk el, akkor a normál értékkinyerési, értékadási és törlési folyamathoz képest más fog a háttérben lezajlani. Ha az osztályattribútum leíró, akkor egy attribútumérték meghatározásakor az interpreter megvizsgálja, hogy az adott attribútumnév által hivatkozott objektum leíró-e, vagyis rendelkezik-e a __get__, __set__, __delete__ metódusok bármelyikével. Ha nem, akkor visszaadja magát a hivatkozott objektumot. Ha viszont igen, akkor a kezdeményezett attribútumműveletnek (értékkinyerés, értékadás vagy törlés) megfelelően lefuttatja a leíróban definiált __get__, __set__ vagy __delete__ metódust.

A mintapéldánkhoz ezt, azaz konkrétan az értékadáskor lefutó __set__ metódust fogjuk kihasználni.

Gondoljuk át mit is szeretnék! Azt, hogy ha a Supermarket példány, vagyis az áruház new_product attribútuma új értéket kap (új termék érkezik), akkor az összes feliratkozó vásárló kapjon értesítést. Ezt eddig úgy valósítottuk meg, hogy a new_product a Supermarket osztályban tulajdonságként (property) volt definálva, és annak setter metódusában lett meghívva a vevők értesítését végző notify_subscribers() metódus. Tehát, ha a new_product új értéket kapott, akkor ez automatikusan lefutott.

Ugyanezt úgy is meg tudjuk valósítani, ha a Product osztályt leíróvá tesszük olyan módon, hogy definiáljuk benne a __set__ metódust, valamint a new_product a Supermarket osztályban – tulajdonság helyett – osztályattribútumként szerepel egy Product példánnyal mint értékkel.

Ebben az esetben a vevők értesítése a __set__ metódusban történhet meg a notify_subscribers() meghívásával, hiszen, ha a new_product egy új Product példányt kap értékként, akkor az interpreter érzékeli, hogy a Product egy attribútumleíró, és automatikusan meghívja a benne definiált __set__ metódust.

Alább láthajuk mindazon osztályokat, amelyeket nem érint az újfajta üzenetküldési megoldás. A Customer osztály kódja a helykímélés miatt nincs feltüntetve, az a korábbi „Értesítés küldés állapotváltozásról üzenetobjektum átadásával – megfigyelő minta, push változat” című bejegyzésben látható itt.

A következő kódsorokban a változással érintett Product és Supermarket osztályokat mutatjuk.

A Supermarket esetében látható, hogy a new_product most nem tulajdonságként, hanem osztályattribútumként van definiálva. /A jobb áttekinthetőség biztosítása érdekében az osztály egyéb kódjai nincsenek megjelenítve, azok a fentebb említett bejegyzésben megtalálhatók/

A Product osztály a korábbihoz képest a __set__ metódussal lett kibővítve. Ennek törzsében a fentebb kifejtett logika jelenik meg néhány kódsorban. Az itt olvasható kommentek segítik a megértést.

A működés tesztprogramja látható alább a megjelenő eredménykiírásokkal együtt.

E bejegyzés az attribútumleírók gyakorlati alkalmazására mutatott egy példát. Az ebben definiált leíró nagyon egyszerű felépítésű, viszont a leírók ennél sokrétűbbek. A leírókról, azok működéséről sokkal részletesebben olvashatunk a Python tudásépítés lépésről lépésre című e-könyv „Attribútumleírók és használatuk” című fejezetben, ahol más alkalmazási példákat is láthatunk.

Érdekel a Python tudásépítés lépésről lépésre az alapoktól az első asztali alkalmazásig című e-könyv.