Kihagyás

UML

Figyelem

Az itt található anyag otthoni tanulásra, feldolgozásra van kitalálva, így ez a gyakorlaton sem kerül leadásra!

A UML (Unified Modeling Language) egy nagyon általános nyelv, melyet a szoftvertervezés és modellezés során alkalmazunk. Nagyon sokféle diagramot lehet elkészíteni a segítségével, ebben a példában az osztálydiagram (Class diagram) és csomagdiagram (Package diagram) kerül bemutatásra. Akit érdekel mit lehet még UML-lel modellezni itt és itt olvashat utána.

Osztálydiagram

Az osztály leírást ad objektumok egy csoportjához, ezeknek közösek az attribútumaik (adattagjaik), operációik, más objektumokkal való kapcsolataik (publikus metódusaik) és szemantikus viselkedésük. Az osztályra gyakorlatilag mintaként tekinthetünk, ami meghatározza a példányosítás során létrejövő objektumo(ka)t. Ezek alapján a UML diagramon az osztályoknak feltüntetjük az adattagjait, metódusait valamint a kapcsolatokat is.

Kapcsolatok

Az osztálydiagramon kapcsolatok segítségével jelöljük az osztályok közötti kapcsolatokat, a majdani objektumok kölcsönhatásait. Ezek a kapcsolatok határozzák meg javarészt a programunk viselkedését. Az osztálydiagramon 3 fő kapcsolat fajtát különböztetünk meg:

  • Asszociáció: legalább az objektumok egyike ismeri a másikat, de (általában) létük nem függ egymástól.
  • Aggregáció: speciális asszociáció, amikor az egyik objektum része a másiknak (létük függ egymástól).
  • Kompozíció: speciális aggregáció, amikor a tartalmazott a tartalmazó nélkül nem létezhet.

A következő ábrán tekinthetjük meg melyik kapcsolatot hogy jelöljük UML-ben. Fontos, hogy aggregáció és kompozíció esetében mindig a tartalmazó osztály felől legyen a szimbólum.

Öröklődés

Osztályok közötti kapcsolat, amikor egy osztály strukúráját / viselkedését megörökli egy vagy több másik osztály. A kapcsolatban résztvevő osztályokat szülő osztálynak (az ős, aki megosztja a struktúráját) és gyerek (aki megörökli a struktúrát) osztály(ok)nak nevezzük. A származott (gyerek) osztály mindent örököl a szülőtől, ezeket kiegészítheti sajátok tulajdonságokkal/operációkkal. Illetve egy örökölt viselkedést is megvalósíthatunk másképp a leszármazott osztályokban. Az öröklödés szülő-leszármazott kapcsolat, de tekinthetünk rá úgy, hogy öröklődési hierarchián felfelé menve (megnézzük az ősosztályt, majd annak ősét, stb.) általánosítást, leszármazottak felé haladva (megnézzük a gyerekosztályt, aztán annak a gyerekét, stb.) pedig specializálást figyelhetünk meg.

Csomagdiagram

Nagy rendszereknél elkerülhetetlen az osztályok csoportosítása. Erre használhatók a csomagok (package), a segítségükkel hierarchikus szerkezetet biztosíthatunk a kódunknak. A csomagok áttekintésére használhatunk csomagdiagramot, melyet szintén UML-ben készíthetünk el. A csomagban lévő osztályok adattagjait/metódusait is fel lehet tüntetni, mint osztálydiagram esetében, azonban ha így túl zsúfolt lenne az ábra azok elhagyhatók. Az is elképzelhető, hogy nem az egész rendszert ábrázoljuk így, csak a benne szereplő 1-2 csomagot, vagy a csomagokat külön-külön. Szerencsére a UML rugalmas annyira, hogy ezt mind megtehetjük. A csomagdiagram lényege, hogy egy absztraktabb, magasabb szintű képet kapjunk a rendszerről.

Feladat

Modellezzük (egy egyszerűsített) osztálydiagrammal és csomagdiagrammal egy online webáruházat, majd valósítsuk is meg Pythonban! Az áruházba jöhetnek vevők, akik megrendelhetnek különböző árucikkeket. Mindenféle árut árulnak, szóval törekedjünk az általánosságra. Többféle fizetési módot is szeretnénk támogatni, ez akár a jövőben bővülhet is. Az alábbiakban a diagram tervezésével párhuzamosan szemléltijük a Python implementációt.

Megoldás

Először is készítsük el az osztályokat a leírás alapján. Ez viszonylag egyszerű, hiszen tudjuk hogy lesznek vevők, rendelés, árucikkek és fizetni is kell valahogy. Ezek alapján a következő osztályaink lesznek (adattagokkal és metódusokkal együtt, amik valószínűleg kelleni fognak):

Pythonban osztályt a class kulcsszó segítségével tudunk létrehozni, az adattagokat az __init__ metódusban elhelyezve adhatunk az osztálynak. Az első osztály, amit elkészítünk, a Vevő osztály lesz.

1
2
3
4
5
class Vevo:
    def __init__(self, nev, cim, penz):
        self.nev = nev
        self.cim = cim
        self.penz = penz

A Rendelés osztály esetében a kifizet metódust egyelőre nem fejtjük ki, csak az egyszerűbbeket.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from datetime import date

class Rendeles:
    def __init__(self, arucikkek):
        self._datum = date.today()  # mai nap lekérése a Python datetime moduljából
        self._reszletek = ''
        self.arucikkek = arucikkek

    def osszegez(self):
        return sum([arucikk.ar + arucikk.szallitasi_koltseg for arucikk in self.arucikkek])

    def osszegez_adoval(self):
        return self.osszegez() * 1.2

    def kifizet(self, vevo, kartyaszam = None, lejarati_datum = None):
        pass

Az Árucikk osztályban megfigyelhető a getter/setter használata Python-ban. Mivel arra törekszünk, hogy szép rendezett kódot írjunk, így az Árucikk osztályt (és a majd belőle származtatott gyerek osztályokat) külön könyvtárba helyezzük (ez Python esetében azt is jelenti, hogy külön csomagba tesszük).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Arucikk:
    def __init__(self, ar, tomeg):
        self.ar = ar
        self._tomeg = tomeg
        self._szallitasi_koltseg = self.szallitast_szamol(tomeg)
        self._nev = ""

    @property
    def szallitasi_koltseg(self):
        return self._szallitasi_koltseg

    @property
    def tomeg(self):
        return self._tomeg

    @tomeg.setter
    def tomeg(self, ertek):
        self._tomeg = ertek
        self._szallitasi_koltseg = self.szallitast_szamol(ertek)


    def szallitast_szamol(self, tomeg):
        return tomeg + (tomeg / 100)

A Fizetés osztályban a fizet metódust csak megadtuk, hogy létezik, viszont nem írtunk semmit a törzsébe. Így szimulálhatjuk a máshonnan ismeretes absztrakt metódust és absztrakt osztályt. Pythonban is van egyébként erre nyelvi támogatás, ez azonban nem része tananyagnak, akit érdekel itt és itt tud bővebben olvasni róla. A Fizetés osztályt szintén külön csomagba szervezzük.

1
2
3
4
5
6
class Fizetes():
    def __init__(self, osszeg):
        self.osszeg = osszeg

    def fizet(self, penz):
        pass

Mivel többféle árucikk lehet a webáruházban és többféle fizetési módot is támogatni szeretnénk, hozzunk létre még 2 fajta árucikket és 2 féle fizetési módot. Hogy kihasználjuk az OOP előnyeit és törekedjünk az általánosításra, a már fent létrehozott Árucikk és Fizetés osztályokból fogjuk ezeket örököltetni.

A Lézerkard osztálynál láthatjuk, hogy a szülő konstruktorának hívásakor a tömeg paraméter értéke mindig 1, viszont az osztály bővült a szín adattaggal.

1
2
3
4
5
6
7
from Arucikk import Arucikk

class Lezerkard(Arucikk):
    def __init__(self, ar, szin):
        super().__init__(ar, 1)
        self.szin = szin
        self._nev = "Lézerkard"
1
2
3
4
5
6
7
from Arucikk import Arucikk

class GumiKalapacs(Arucikk):
    def __init__(self, ar, tomeg, fej_meret):
        super().__init__(ar, tomeg)
        self.fej_meret = fej_meret
        self._nev = "Gumi kalapács"
1
2
3
4
5
6
7
8
from Fizetes import Fizetes

class Keszpenz(Fizetes):
    def __init__(self, osszeg):
        super().__init__(osszeg)

    def fizet(self, penz):
        return penz > self.osszeg

Hogy az adott kártyaszám helyes-e, egy reguláris kifejezés segítségével ellenőrizzük. Erről bővebben itt olvashatunk, illetve más tantárgyak esetében is találkozhattunk már vele.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from Fizetes import Fizetes
import re

class KartyasFizetes(Fizetes):
    def __init__(self, osszeg, kartyaszam, lejarati_datum):
        super().__init__(osszeg)
        self._kartyaszam = kartyaszam
        self._lejarati_datum = lejarati_datum

    def fizet(self, penz):
        return self.letezo_kartya() and penz > self.osszeg

    def letezo_kartya(self):
        return re.match('([0-9]{4}-){3}[0-9]{4}', self._kartyaszam) is not None

Az elkészült projekt mappaszerkezete a következő: - main.py (majd itt fogjuk kipróbálni amit csináltunk) - Rendeles.py (a rendelést reprezentáló osztály) - Vevo.py (a vevőt reprezentáló osztály) - Arucikk (könyvtár - csomag) - Arucikk.py (ősosztály) - GumiKalapacs.py (gyerek osztály) - Lezerkard.py (gyerek osztály) - Fizetes (könyvtár - csomag) - Fizetes.py (ősosztály) - KartyasFizetes.py (gyerek osztály) - Keszpenz.py (gyerek osztály)

Már csak a kapcsolatokat kell meghatároznunk az osztályok között. A Rendelés osztály köti össze a Fizetés-t a Vevő-vel és itt egyik esetben sem beszélhetünk tartalmazásról, ezek csak "ismerik egymást", kommunikálnak egymással. Így ezek között asszociáció lesz a megfelelő kapcsolat. A rendelés viszont tartalmazza az árucikkeket, viszont annak léte nem függ az árucikkektől. Így itt egyszerű aggregációról van szó. Az elkészült osztálydiagramot a következő ábrán lehet megtekinteni.

Az objektumok kölcsönhatása, vagyis az asszociáció megvalósulása legfőképp a kifizet metódusban nyilvánul meg. Ennek törzse a következőképp néz ki:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 def kifizet(self, vevo, kartyaszam = None, lejarati_datum = None):
     osszeg = self.osszegez_adoval()

     if kartyaszam is None or lejarati_datum is None:
         self._reszletek += f'{vevo.nev} készpénzzel kíván fizetni.\n'
         fizetes = Keszpenz(osszeg)
     else:
         self._reszletek += f'{vevo.nev} kártyával ({kartyaszam, lejarati_datum}) kíván fizetni.\n'
         fizetes = KartyasFizetes(osszeg, kartyaszam, lejarati_datum)

     siker = fizetes.fizet(vevo.penz)

     if siker:
         self._reszletek += f'Sikeres fizetés!\n'
         self._reszletek += f'A megvásárolt árucikkek: {", ".join([arucikk.__str__() for arucikk in self.arucikkek])}\n'
         vevo.penz -= osszeg
     else:
         self._reszletek += f'Sikertelen fizetés! :(\n'

Bár a feladat bonyolultsága nem indokolja a csomagdiagram külön ábrázolását (és a benne lévő osztályok leegyszerűsítését), a példa kedvéért elkészítettük ezt is. Figyeljük meg a csomagok közötti kommunikációt!

Készítsünk is egy egyszerű tesztet a programhoz! Figyeljünk az importokra, az árucikkek külön csomagban vannak.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from Arucikk.GumiKalapacs import GumiKalapacs
from Arucikk.Lezerkard import Lezerkard
from Rendeles import Rendeles
from Vevo import Vevo

ani = Vevo("Anakin Skywalker", "Tatooine 12", 250)
obi_wan = Vevo("Obi-Wan Kanobi", "Stewjon 4", 350)

rendeles1 = Rendeles([GumiKalapacs(25, 5, 50), Lezerkard(100, "zöld")])
rendeles2 = Rendeles([Lezerkard(120, "kék")])

rendeles1.kifizet(ani)
rendeles2.kifizet(obi_wan, "5133-3367-8912-3456", "02/22")

print(rendeles1)
print(rendeles2)

Kimenet

Anakin Skywalker készpénzzel kíván fizetni. Sikertelen fizetés! :(

Obi-Wan Kanobi kártyával (('5133-3367-8912-3456', '02/22')) kíván fizetni. Sikeres fizetés! A megvásárolt árucikkek: Lézerkard


Utolsó frissítés: 2021-10-04 17:35:04