Az előző bejegyzésben azt a helyzetet modelleztük, amikor egy áruház új termék érkezésekor értesíti a címüket megadó vásárlókat, lehetőséget adva, hogy ha az adott termék, az adott áron érdekli őket, akkor meg tudják vásárolni. Láttuk, hogy a modellben csak a vásárlási hajlandóságot tudtuk megjeleníteni, aminek oka az, hogy a potenciális vásárlók csak a termékinformációt (terméknév és ár) kapták meg, de azt nem, hogy hol, kitől, milyen elérhetőségen tudják megvásárolni az árut.
Ahhoz, tehát, hogy ténylegesen megvehessék és birtokolhassák a felkínált terméket az kell, hogy az áruház elérhetőségét, azaz referenciáját is megkapják. Ahogy szó volt róla, a modellként alkalmazott programtervezési mintában ezt két módon is megtehetjük: ha a vevők tájékoztatásakor adjuk át az elérhetőségi információt, akkor beszélünk Push változatról, ha pedig a vásárló állandó jelleggel, például a hírlevélre való feliratkozáskor kapja meg a szupermarket elérhetőségét, akkor Pull változatról van szó.
Most az előző bejegyzésben szereplő Push változatot fejlesztjük tovább úgy, hogy az értesítéskor a vevő a termékadatokkal együtt megkapja az áruház objektumreferenciáját is. Ezt lehetne úgy megtenni, hogy kiegészítjük az értesítést végrehajtó notify_subscribers(), valamint az update() metódusokat még egy, a feladóra/küldőre vonatkozó argumentummal. Ennél rugalmasabb megoldás, és a valóságot is jobban modellezi, ha egy külön üzenet objektumban, annak attribútumaiként definiálva, adjuk át a releváns adatokat. Ehhez létrehozunk egy PromotionMessage nevű osztályt, amelyet jelen esetben – mivel ennek példányai létrehozása után nem kell azok attribútumait változtatni – egyszerűen egy mezőneves tuple-lal definiálunk.
Ezek után nem kell mást tenni, mint a Supermarket osztályban a new_product setter() metóduson belül a notify_subscribers() hívásakor a PromotionMessage egy példányát átadni, ahol a konstruktorban szerepeltetjük a küldőként magát a Supermarket példányt (ami a self), valamint az új termék nevét és árát. Ezt követhetjük nyomon 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 |
from publisher_push import Publisher, Subscriber from collections import namedtuple # A terméket nevével és árával modellező osztály. class Product: def __init__(self, name, price): self.name = name self.price = price def __repr__(self): return '{}({}, {})'.format(type(self).__name__, self.name, self.price) # A kiküldött promóciós üzenetet (pl. hírlevél, újság) modellező osztály. PromotionMessage = namedtuple('PromotionMessage', 'sender product_name product_price') class Supermarket(Publisher): def __init__(self): super().__init__() # Az áruház kezdő árukészlete. Ezekre nincs hirdetés. self._products = [Product('liszt', 360), Product('cukor', 270), Product('bor', 1500)] self._new_product = None @property def new_product(self): return self._new_product @new_product.setter def new_product(self, product: Product): self._new_product = product # Az új terméket hozzáadjuk az áruház termékkínálatához. self._products.append(product) # Új termék érkezéséről tájékoztató üzenet küldés a feliratkozóknak. A hírlevélben szerepel a # az áruház elérhetősége (referenciája), valamint a termék neve és ára. self.notify_subscribers(PromotionMessage(self, product.name, product.price)) def sell(self, product_name, sell_price): """Az áruház a megadott nevű terméket a megadott áron eladja, feltéve, hogy ilyen néven és legfeljebb ilyen áron van termék készleten. Az eladott termék lesz a visszatérési érték. Ha nem lehet eladni, akkor None. Az eladott termék kikerül az áruház termékraktárából. """ for prod in self._products: if prod.name == product_name and prod.price <= sell_price: product_to_sell = prod self._products.remove(product_to_sell) product_to_sell.price = sell_price return product_to_sell |
A Customer osztályban pedig az update() metódust módosítjuk úgy, hogy egyetlen argumentumot fog fogadni, ami a PromotionMessage példánya. Ebből ki tudja nyerni nem csak a terméknevet és árat, hanem a küldő áruház hivatkozását is. Ha a vevőt érdekli a termék és árban is megfelelő neki, akkor elindítja a vásárlási folyamatot a saját buy() metódusának meghívásával átadva azt, hogy kitől, mit és mennyiért kíván megvenni.
A buy() metódus az áruház referenciájára meghívja annak sell() metódusát, megkérve az áruházat, hogy az adott terméket az adott áron adja oda. A sell() metódus csak akkor adja el (vagyis visszatérési értékként szolgáltatja) a terméket, ha a termék raktáron van (pl. más még nem vette meg), és a vételi ár nem kisebb, mint az eladási ár. Ha e feltételek valamelyike nem teljesül, akkor nem ad vissza semmit, pontosabban None lesz a visszatérési érték. A konkrét kódolást az alábbi osztálydefiníciójában láthatjuk.
|
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 |
class Customer(Subscriber): def __init__(self, name: str, **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 # Megvasárolt termékek. self.purchased_products = set() def update(self, message: PromotionMessage): """Termékpromóciós tájékoztató fogadása és a benne foglalt termék megvásárlása, ha az igenyeknek megfelel.""" # 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 message.product_name in self._demanded_products_with_max_price: if message.product_price <= self._demanded_products_with_max_price.get(message.product_name): self.buy(message.sender, message.product_name, message.product_price) else: print(f'{self.name}: érdekel a {message.product_name}, de számomra még túl drága.') def buy(self, shop: Supermarket, product_name: str, buy_price: float): """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.""" product = shop.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.') |
Mivel az eladott terméket az eladási áron értékesíti az áruház, ezért mielőtt a sell() metódus azt visszaadná, a termék price attribútumát az eladási árra kell változtatni. Mivel ez az objektum módosíthatóságát igényli, ezért eltérően az előző bejegyzésben alkalmazott megvalósítástól (nem változtatható mezőneves tuple), most normál osztályként definiáljuk a Product osztályt.
Vegyük észre, hogy az öröklött Subscriber és Publisher osztályokon nem kellett változtatni, így azok kódját most nem tüntettük fel, ezek az előző bejegyzésben láthatók.
A tesztsorok és a kiírt eredmények mutatják az új termékről történő tájékoztatás következtében az egyes vásárlók reakcióit, valamint a végén láthatjuk azt is, hogy miket vásároltak.
|
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 |
# 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 vevők feliratkoznak a szupermarket hírlevelére. for customer in customers: supermarket.add_subscriber(customer) # 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)): 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', 7000) # Kiírjuk az egyes vevők által megvásárolt termékeket. 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. # Éva megvásárolt termékei: Product(bor, 1800) Product(kenyér, 950) # Ádám megvásárolt termékei: Product(sör, 590) Product(bor, 1800) |
A következő bejegyzésben a Pull változatot mutatjuk be.
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ás osztályokkal” fejezet, a „Készétel fogyasztás a szabványos könyvtár moduljainak használata” fejezet „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.