Az előző két bejegyzésben egy áruház hírlevelére feliratkozó vásárlók új termék érkezéséről való tájékoztatását, és ennek alapján történő vásárlásokat modelleztük és kódoltuk a megfigyelő tervezési minta Push változata alapján.
Most ugyanezt a feladatot a megfigyelő minta Pull változatával oldjuk meg. Ahogy arról az előzőekben szó volt, a Pull változat esetén a vevő előre megkapja az áruház referenciáját és így csupán arról kell értesülnie, hogy jött valamilyen új termék, de nem szükséges a termék adatait (név, ár) megkapnia az értesítéssel együtt, mert az értesítés után ezeket maga kérdezi le az áruháztól, hiszen rendelkezik annak elérhetőségével.
A vevő két módon is megkaphatja előre az áruház referenciáját. Az egyik, hogy definiálunk számára egy feliratkozást megvalósító subscribe() metódust, aminek argumentuma az áruház hivatkozása. Ezt a metódust futás közben bármikor meghívhatjuk, és onnantól kezdve kapni fogja az értesítéseket. A másik lehetőség, hogy a példányosításkor a konstruktorban adjuk meg az áruház referenciáját. Egyszerre akár mindkét módot is biztosíthatjuk a Customer osztályban. Ezt láthatjuk alább.
|
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 |
from publisher_push import Publisher, Subscriber from collections import namedtuple class Product:... PromotionMessage = namedtuple('PromotionMessage', 'sender product_name product_price') class Supermarket(Publisher):... class Customer(Subscriber): def __init__(self, name: str, supermarket_to_subscribe: Supermarket = None, **demanded_products_with_max_price): self.name = name # A vásárló neve # A vásárló termékigényei a számára még elfogadható árakkal. self._demanded_products_with_max_price = demanded_products_with_max_price # A vevő feliratkozik az áruház hírlevelére, ha meg lett az áruház adva. self.subscribe(supermarket_to_subscribe) # Megvasárolt termékek. self.purchased_products = set() def subscribe(self, supermarket: Supermarket): if isinstance(supermarket, Supermarket): self.supermarket = supermarket self.supermarket.add_subscriber(self) def update(self, message: PromotionMessage=None): """Termékpromóciós tájékoztató opcionális fogadása és a benne foglalt termék megvásárlása, ha az igenyeknek megfelel.""" # A vásárló ismeri az áruház referenciáját, így az új termék nevét és árát az áruháznál megérdeklődi. product_name, product_price = self.supermarket.new_product.name, self.supermarket.new_product.price # Amennyiben a termékpromóciós tájékoztatóban levő termék szerepel az igényei között, és # nem drágább, mint amit hajlandó kifizetni érte, akkor megvásárolja az üzenet küldőjétől mint áruháztól. if product_name in self._demanded_products_with_max_price: if product_price <= self._demanded_products_with_max_price.get(product_name): self.buy(product_name, product_price) else: print(f'{self.name}: érdekel a {product_name}, de számomra még túl drága.') def buy(self, product_name: str, buy_price: float, shop: Supermarket = None): """A megadott nevű terméket megvásárolja az adott áron, az adott áruháztól, majd a megvásárolt termékek közé teszi.""" if isinstance(shop, Supermarket): product = shop.sell(product_name, buy_price) else: product = self.supermarket.sell(product_name, buy_price) if product is not None: self.purchased_products.add(product) print(f'{self.name}: {product.name} megvéve {buy_price}Ft áron.') |
Természetesen nem elegendő az __init__ módosítása és az új subscribe() metódus definiálása, hanem némi módosítást kell tenni az update() és buy() metódusokban is.
Az update() esetén, mivel most nem szükséges a message argumentum, ezért ezt opcionálissá tesszük. Azért nem töröljük, mert így a Supermarket osztály notify_subscribers() metódusa továbbra is átadhatja a hírlevelet modellező PromotionMessage példányt az update()-nek, ami azért jó, mert ha vannak Push üzemmódhoz tartozó feliratkozók, akkor ők változatlanul megkaphatják a hírleveleket.
A Customer osztályban az update() törzsében annyi változtatást kell tenni, hogy most nem a message objektumból nyerjük ki a termékadatokat, hanem – mivel a Customer példány rendelkezik az áruház referenciájával – az áruháztól kérdezzük le az új termék, azaz a new_product attribútumból a terméknevet és árat. A metódus törzs további részében pedig ezeket használjuk fel.
A buy() metódust is módosítani kell. Egyrészt az áruházat képviselő argumentumot opcionálissá tesszük. Ezt azért, hogy ha nem iratkozik fel a vásárló, akkor is tudjon vásárolni, persze csak akkor, ha az argumentumban megkapja az áruházpéldányt. A buy() metódus törzsében pedig egy feltételes elágazással eldöntjük, hogy kitől fogunk vásárolni. Ha az argumentumban megadott az áruház, akkor attól, ha pedig nem, akkor a tárolt áruházreferenciát vesszük és azon keresztül. A metódus többi kódja a Push változathoz képest nem változik.
Vegyük észre, hogy a Pull változat megvalósításához csak a Customer osztályt kellett módosítani a többit (Subscriber, Publisher, Product, PromotionMessage és Supermarket) nem, ezért ezek definícióját nem is tüntettük fel, azok az előző bejegyzésekben találhatók.
Az alább látható tesztfuttatásnál mindegyik vevőfaját bemutattuk. Két vevő a subscribe() metódusának meghívásával iratkozik fel, és egy a létrehozásakor kapja meg az áruház referenciáját. Egy további pedig feliratkozás, vagyis értesítés nélkül vásárol. A működés helyességét a kiírások mutatják.
|
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 |
# TESZT supermarket = Supermarket() customers = [Customer('Éva', alma=380, szappan=450, párna=8000, kenyér=1000, bor=1900), Customer('Ádám', kolbász=5000, borotva=1500, sör=600, kenyér=900, bor=2000)] # A két vevő feliratkozik a szupermarket hírlevelére. for customer in customers: customer.subscribe(supermarket) # Egy vevő a létrejöttekor iratkozik fel. customers.append(Customer('Béla', supermarket, szappan=500, kolbász=5200)) # A szupermarket új termékeket szerez be, és adott áron kínálja. Erről rögtön tájékoztatja a hírlevélre felirtakozókat. for product in (Product('kenyér', 950), Product('sör', 590), Product('párna', 8100), Product('bor', 1800), Product('kolbász', 5100)): supermarket.new_product = product # Az egyik vevő leiratkozik a hírlevélről, így újabb, számára kedvező ajánlatokról nem értesül és ezért nem is vásárol. supermarket.remove_subscriber(customers[0]) supermarket.new_product = Product('párna', 7900) # Egy vásárló feliratkozás és értesítés nélkül vásárol az áruházban. customers.append(Customer('Cecil', párna=8500)) customers[-1].buy('párna', 7900, supermarket) # A vásárlók által megvett termékek. for customer in customers: print(f'{customer.name} megvásárolt termékei:', *customer.purchased_products) # Eredmények: # Éva: kenyér megvéve 950Ft áron. # Ádám: érdekel a kenyér, de számomra még túl drága. # Ádám: sör megvéve 590Ft áron. # Éva: érdekel a párna, de számomra még túl drága. # Éva: bor megvéve 1800Ft áron. # Ádám: bor megvéve 1800Ft áron. # Béla: kolbász megvéve 5100Ft áron. # Ádám: érdekel a kolbász, de számomra még túl drága. # Cecil: párna megvéve 7900Ft áron. # Éva megvásárolt termékei: Product(kenyér, 950) Product(bor, 1800) # Ádám megvásárolt termékei: Product(bor, 1800) Product(sör, 590) # Béla megvásárolt termékei: Product(kolbász, 5100) # Cecil megvásárolt termékei: Product(párna, 7900) |
E cikk témájához a Python tudásépítés lépésről lépésre című e-könyv következő részei kapcsolódnak: az „Osztály vigyázz! – típuslétrehozásosztályokkal” fejezet, a „Készétel fogyasztás a szabványos könyvtár moduljainak használata” fejezet „Absztrakt osztályok” alfejezet, valamint a „Speciális konténer típusok” alfejezetén belül a „Mezőneves tuple – namedtuple” cím, az „Attribútumműveletek kontrollált végrehajtása” fejezeten belül a „Tulajdonságok létrehozása” és „Dekorátorral létrehozott tulajdonságok” alfejezetek.