A kód rugalmasságán most azt értjük, hogy egy újabb igény esetén mennyire kell a már meglévő kódba belenyúlni, vagyis változtatni azon. Az ilyen változásokkal szemben akkor rugalmas a kód, ha az új igény kielégítése megoldható úgy, hogy csak új kódsorokkal kell kiegészíteni, de nem kell módosítani a már létezőn. A programtervezésben ezt magyarul nyitottság-zártság elvnek (open-closed principle) nevezik, ami tehát arra utal, hogy egy jól tervezett programkód nyitott a kiterjesztésre, azaz újabb kódokkal történő bővítésre, de zárt a meglévő kód módosítására. Ugyanis, ha a már egyszer letesztelt és jól működő kódot módosítani kell, akkor hibalehetőséget vihetünk be, és ezért a programot újra kell tesztelni, ami egy összetett, nagyon sok programsorból álló szoftvernél nem kis munkát és időráfordítást igényel.
Tegyük fel, hogy egy hulladékfeldolgozó üzemet akarunk modellezni. Az üzem kezdetben csak fémhulladékot hasznosít. Ezért az üzemet modellező osztályban egyetlen, feldolgoz(self, hulladék:Fém) fejlécű metódust definiálunk. Később azonban az üzem képes lesz műanyaghulladék feldolgozására is. Kérdés, hogyan kövessük le ezt az új képességet az osztálydefinícióban?
Egy lehetőség, hogy a feldolgoz() metódust átalakítjuk úgy, hogy argumentuma lehet fém és műanyag is, és a metódustörzsben egy típusvizsgálat alapján feltételes elágazással dolgozzuk fel vagy a fémet vagy a műanyagot. E megoldás azonban nem felel meg a nyitottság-zártság elvnek. Minden egyes újabb és újabb fajtájú hulladék feldolgozhatóságához bele kell nyúlni a metódustörzsbe, hogy egy új feltételes ágat alakítsunk ki.
Másik lehetőség, hogy minden egyes hulladékfajtát egy-egy külön metódussal dolgozunk fel. Ez elvben már rugalmas kódot eredményez, de a tényleges megvalósításkor adódnak gondok. Ez abból fakad, hogy a Pythonban, ha több függvényt vagy metódust ugyanolyan névvel definiálunk, akkor mindig az utoljára definiált lesz érvényes. Vagyis hiába implementálunk két metódust feldolgoz(self, hulladék:Fém) és feldolgoz(self, hulladék:Műanyag) fejléccel ebből híváskor csak az egyik fog lefutni.
Ha ez így van, akkor jobb ötlet híján két különböző nevű metódust teszünk az osztályba mondjuk feldolgoz_fém(self, hulladék:Fém) és feldolgoz_műanyag(self, hulladék:Műanyag) fejléccel. A problémát ezzel az osztályban ugyan megoldottuk, de most meg a hívó programban kell módosításokat eszközölni, hiszen eddig a feldolgoz() metódus lett meghívva. Ez akkor különösen problémás, ha a hívó program nem a mi kezelésünkben van.
Tudomásul vesszük a nem ideális helyzetet, vagy van mégis valami megoldás?
A jó hír, hogy ebben az esetben a gondunkra van gyógyír, amit a szabványos könyvtár functools moduljában található singledispatch és singledispatchmethod hívható objektumok biztosítanak. Ezekkel egy adott nevű és paraméterkészletű függvény vagy metódus definícióját kell dekorálni, és utána egy-egy konkrétan implementált változatot készíteni az első argumentum típusának megfelelően. A használat pontos részleteit a Python tudásépítés lépésről lépésre című e-könyv „Függvények és metódusok azonos névvel, de eltérő paraméter típussal” című fejezetében megtaláljuk. Most ezt a hulladékfeldolgozó példáján mutatjuk be az alábbi programkódban.
|
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 |
from functools import singledispatchmethod # Hulladéktípusok class FémHulladék: ... class MűanyagHulladék: ... class Hulladékfeldolgozó: @singledispatchmethod def feldolgoz(self, hulladék): raise NotImplementedError('Ez a fajta hulladék nem dolgozható fel.') @feldolgoz.register(FémHulladék) def _(self, hulladék): print('Fémhulladék feldolgozása...') @feldolgoz.register(MűanyagHulladék) def _(self, hulladék): print('Műanyaghulladék feldolgozása...') # TESZT hulladékfeldolgozó = Hulladékfeldolgozó() lomok = (FémHulladék(), MűanyagHulladék()) # Feldolgozást végző kód, ami a hulladékfajtáktól függetlenül ugyanaz marad. for hulladék in lomok: hulladékfeldolgozó.feldolgoz(hulladék) # Eredmény: # Fémhulladék feldolgozása... # Műanyaghulladék feldolgozása... |
E módszerrel elértünk, hogy új hulladéktípus esetén csak kiegészíteni kell a kódot az új hulladék osztályával, valamint a hulladékfeldolgozó üzem osztályát egy új metódusimplementációval. Mindezt úgy, hogy a tesztsoroknál a feldolgoz() metódust hívó kódon nem kell változtatni. A Pythonban ezzel a módszerrel tehát lehetőségünk van arra, hogy egy függvény- vagy metódusnevet többször is (az első argumentumok eltérő típusa szerint) felhasználjunk, szakszóval túlterheljünk (overloading).