Szükségünk van az Aspektus-orientált programozásra?
JELEN DOLGOZAT AZ INNOVÁCIÓS ÉS TECHNOLÓGIAI MINISZTÉRIUM ÚNKP-19–1 KÓDSZÁMÚ ÚJ NEMZETI KIVÁLÓSÁG PROGRAMJÁNAK SZAKMAI TÁMOGATÁSÁVAL KÉSZÜLT.
You can find the english version here.
Akárcsak a művészetben, a szoftverfejlesztésben is minden korszaknak megvan a maga uralkodó paradigmája. A paradigmák közötti váltások nem egy adott időpontban és helyen történnek, hanem a paradigmák egymás mellett léteznek, nem különíthetőek el önálló korszakokra. Az új paradigmák mindig igyekeznek megtartani elődjeik jó tulajdonságait, levetkőzni a rosszakat és valamiképpen átlépni az elődök határait.
Hiszed vagy sem, az Objektum-orientált programozásnak (OOP) is megvannak a saját határai. Ebben a rövid cikkben az Aspektus-orientált programozással(AOP) fogunk foglalkozni, amely megpróbálja semlegesíteni az OOP hátrányait és megoldásokat adni bizonyos korlátaira az aspektusok használatával.
Az aspektus olyan, mint egy osztály, tartalmazhat metódusokat, mezőket és lehet abstract. A legfontosabb tulajdonsága azonban az, hogy egyszerre több osztályra is hatással lehet az üzleti logikában.
Az AOP a Vonatkozások szétválasztásának (Separation of Concerns, SoC) elvén alapszik, ami azt jelenti, hogy a vonatkozásokat, funkciókat jól elkülöníthető és könnyen menedzselhető részekre kell felbontani. Az elkülönítés alapja maga a vonatkozás, vagyis hogy a program melyik részéért felelős az adott kódrészlet.
A SoC kimondja, hogy ha két vonatkozás, például komponensek vagy osztályok stb. elkülöníthetőek egymástól, akkor ezt érdemes megtenni. Például a Builder programtervezési minta elkülöníti egymástól egy komplex objektum felépítését annak megjelenítésétől. (1)
Az AOP mögötti ötlet az, hogy készítsünk az OOP felett egy új absztrakciós szintet, amely képes felelni azokért a dolgokért, amelyekre az üzleti logikában sok különböző helyen szükségünk van. Az AOP segítségével ezeket a kódrészleteket ki tudjuk emelni az üzleti logikából és ugyanúgy egységbe zárni egy jól áttekinthető helyre, mint az üzleti logikát az OOP-vel.
Egyszerű Példa
Képzeld el, hogy készítettél egy gyönyörű objektum-orientált programot. Itt van például ez az egyszerű alkalmazás, amely tea filter rendeléseket kezel. A felhasználó tetszőlegesen testre szabhatja a teáját, aztán ezt meg is rendelheti. Emellett a programnak logolnia kell, amikor a felhasználó elvégezte a testreszabást. A logolás implementációja a következő az üzleti logikában (a mezők és property-k nincsenek megjelenítve):
import java.util.logging.Logger;
import java.util.logging.Level;
public class Tea {
Logger logger = Logger.getLogger(Tea.class.getName());
public Tea(String name, String taste, int packet) {
this.setName(name);
this.setTaste(taste);
this.setPacket(packet); logger.log(Level.INFO, “The user customized tea pocket!”);
}
public void order(Tea tea) {
System.out.println(“You ordered “ + tea.getPacket()
+ “ packet “ + tea.getName() + “ “ + tea.getTaste() + “!”);
}
}
Remekül működik, de mi van, ha azt is logolni akarod, amikor a felhasználó leadja a rendelését? Nyilvánvalóan implementálnod kell egy nagyon hasonló kódsort ide:
public void order(Tea tea) {
System.out.println(“You ordered “ + tea.getPacket()
+ “ packet “ + tea.getName() + “ “ + tea.getTaste() + “!”);
logger.log(Level.INFO, “The user ordered tea pocket(s)!”);
}
További hasonló kódokat kellene beszúrnod mindenhová, ahol logolni akarsz.
Egy másik probléma ezzel, hogy az üzleti logika és a logolás összefonódik egymással. A logolás összepiszkítja az üzleti logika kódjait, ami ellentétes két SOLID elvvel, név szerint az Egy Felelősség Elvével (Single Responsibility Principle, SRP) és a Nyitva/Zárt Elvvel (Open/Closed Principle, OCP).
SRP
Az SRP alapján
biztosnak kell lennünk abban, hogy az osztályainknak egy és csakis egy felelősségi körül van. […] Ha az osztálynak több felelősségi köre van, (1)
akkor gyakrabban kell rajta változtatnunk, mint szükséges lenne.
Ha az osztálynak egynél kevesebb felelősségi köre van , akkor kettő vagy több osztály osztozik ugyanazon a felelősségi körön, (1)
vagyis ugyanazon dolog miatt több osztályt is módosítanunk kell, mint szükséges lenne.
Ezek az összefonódó funkciók pontosan ezt csinálják és megsértik az SRP-t, mindkét irányból: az osztálynak egyszerre van több felelősségi köre, és több osztály is osztozik azonos felelősségi körön.
Például, ha készítünk a Tea mintájára egy Beer és egy Wine osztályt, akkor ezek az osztályok az üzleti logikához tartozó saját, szerves felelősségeiken túl megosztják egymás között a logolás felelősségét is.
Furcsa, hogy az OOP az egységbezárásról (encapsulation) szól, most mégis van egy olyan funkciónk, amire tulajdonképpen minden “kapszulában” szükségünk van, vagyis mindegyikben implementálnunk kell.
Biztos ez?
Korántsem.
Az AOP kiegészíti az OOP-t
Az AOP arról szól — többek között — , hogy a fentihez hasonló OOP tervezési problémákat megoldja. Valójában az AOP remekül kiegészíti az OOP-t azzal, hogy az SoC-t használja a kód modularizációjának növeléséhez.
Amikor OOP alapokon tervezel, akkor az adatokkal és az adatokon végzett műveletekkel foglalkozol, ezeket zárod egységekbe, általában osztályokba. Ezzel a módszerrel viszont lehetséges, hogy figyelmen kívül hagysz néhány másik tervezési lehetőséget, szóval bizonyos funkcióid össze-vissza lesznek a kódban. Ezek lehetnek üzleti logikához kapcsolódóak, és mellékesek is. (Lásd az Egyszerű Példát, ahol megszegtük az SRP-t). Ebből következhet, hogy ezeket a kódrészleteket nehéz lesz debugolni, tesztelni és refactorálni.
Ezeket a kódrészleteket, funkciókat átszövő vonatkozásoknak nevezzük. A logolás, a szinkronizáció, a monitorozás, az autentikáció, a pufferelés, a tranzakció-kezelés és a környezetfüggő hibakezelés a legjobb példák rá. (2)
Amikor AOP alapokon tervezel, akkor összetartozó feladatokkal és funkciókkal foglalkozol. Megteheted, mert az adatokkal és a rajtuk végzett műveletekkel már végeztél, amikor az OOP tervezést csináltad.
Ez azt is jelenti, hogy nem tudsz nulláról felépíteni egy rendszert csak az AOP-ra építve, de nem is kell.
Az AOP ugyanis együttműködik az OOP-vel és felhasználja az eszközkészletét is: osztályokat, metódusokat, objektumokat. Ezért lehetséges először OOP-t használva implementálni a programot. Ezután foglalkozhatsz az átszövő vonatkozásokkal és implementálhatod az aspektusokat. Végül a futtatható kód az OOP alapú üzleti logika és az aspektusok egyvelege lesz.
Nagyon fontos, hogy AOP-vel nem csak lényegtelen részletek emelhetők ki az üzleti logikából, hanem környezetfüggő viselkedéssel is fel lehet ruházni az objektumokat. Ez OOP-vel nem lehetséges, mert az az egységbezárásról szól, nem a feladatok végrehajtásának szétválasztásáról. Az OOP célja, hogy megvédje az objektumokat a környezetük hatásától annak érdekében, hogy az objektumok az irányításunk alatt maradhassanak.
Az AOP viszont lehetővé teszi az objektumoknak a környezetfüggő viselkedést anélkül, hogy elveszítenénk a kontrollt.
OCP
Rendkívül fontos, hogy az OOP alapokon elkészített kódnak egyáltalán semmit nem kell tudnia az aspektusokról. Ez azt is jelenti, hogy ezeken a kódokon nem kell változtatni ahhoz, hogy aspektusokat adhassunk hozzá és együtt tudjanak működni. (2)
Az OCP kimondja, hogy
a kódbázisnak nyitottnak kell lennie a bővítésre, de zártnak a módosításra. (1)
Például, ha hozzá akarod adni az üzleti logikához a logolást, akkor előfordulhat, hogy ennek a kódját már létező és működő metódusba kell beillesztened, ami ellentétes az OCP-vel. Ehelyett jobb megoldás, ha csinálsz egy aspektust és egy új struktúrát és ide implementálod a logolást.
A probléma az átszövő vonatkozásokkal
- Mindenhol implementálnod kell őket, ahol szükséged van rájuk.
- Összepiszkítják az üzleti logika felelősségi köreit, vagyis ellentétes az SRP-vel.
- Szétszóródva vannak jelen a kódban, ami szintén ellentétes az SRP-vel.
- Amikor beilleszted őket a kódba, az ellentétes lehet az OCP-vel.
Az AOP megoldja ezeket a problémákat az aspektusok vonatkozásával. Ha AOP-t használsz, akkor:
- Sokkal átláthatóbb kódot fejleszthetsz.
- Az átszövő vonatkozások is egységbe lesznek zárva, ahelyett, hogy össze-vissza lennének az üzleti logikában. Ez megfelel az SRP-nek.
- A használatához nem kell változtatnod az eredeti kódon. Ez megfelel az OCP-nek.
- Akármikor hozzáadhatsz aspektusokat. Ez is megfelel az OCP-nek.
Ha AOP-t használsz, könnyűszerrel egységbe zárhatsz alapvető vagy lényegtelen kódrészleteket is egy saját osztályban, amit aspektusnak nevezünk. Emiatt az üzleti logikád felelőssége tiszta maradhat és neked sem kell olyan nem szervesen ide tartozó dolgokat ide implementálni, mint a logolás.
Az Egyszerű Példa refactorálása
Az AOP terminológia megismeréséhez olvasd el ezt a cikket.
Az üzleti logika a következő:
public class Tea {
public Tea(String name, String taste, int packet) {
this.setName(name);
this.setTaste(taste);
this.setPacket(packet);
}
public void order(Tea tea) {
System.out.println(“You ordered “ + tea.getPacket()
+ “ packet “ + tea.getName() + “ “
+ tea.getTaste() + “!”);
}
}
Láthatod, hogy egész pontosan nulla sor szól a logolásról. Emellett az üzleti logika rész semmit nem változott az eredetihez képest.
A logolás a következő:
import java.util.logging.Logger;
import java.util.logging.Level;public aspect LogAspect {
Logger logger = Logger.getLogger(Tea.class.getName());
pointcut customizeTeaLog():
call(Tea.new(String, String, int));
after(): customizeTeaLog() {
logger.log(Level.INFO, “The user customized tea pocket!”);
}
pointcut orderTeaLog(): call(void order(*));
after(): orderTeaLog() {
logger.log(Level.INFO, “The user ordered tea pocket(s)!”);
}
}
Ahogy korábban említettem, az üzleti logikának semmit nem kell tudnia az aspektusról, működnie kell vele és nélküle is. Most aspektus használatával sikeresen szétválasztottuk az átszövő vonatkozást (logolás) az üzleti logikától, sőt, egységbe is tudtuk zárni. Ha a Tea konstruktora vagy az order() függvény meghívódik, a hozzá kapcsolódó advice le fog futni.
Összegzés
Az AOP használatának következményei:
- Sokkal átláthatóbb és tisztább forráskódot eredményez.
- Szétválasztja a lényegest a lényegtelentől, az üzleti logikát a mellékestől.
- Lehetővé teszi az objektumoknak a környezetfüggő viselkedést anélkül, hogy elveszítenénk felettük a kontrollt.
- Új perspektívákat nyit a rendszerek tervezésében.
- Megerősíti az SRP és OCP jelenlétét a forráskódban.
- Egyszerűsíti az átszövő vonatkozások kezelését.
- Anélkül alkalmazható, hogy az eredeti kódban bármit változtatni kellene.
- Az üzleti logika vele és nélküle is képes működni.
Az egyetlen nehézséget a szintaxis és egy új absztrakciós szinten való gondolkodás elsajátítása okozhatja. De ha ismered az OOP-t, akkor könnyen felismered az átszövő vonatkozásokat, amikre alkalmazhatod az AOP-t.
Tőled függ, mire használod az AOP-t. Kiemelheted akár az üzleti logika, akár egy mellékes dolog átszövő vonatkozását, legyen az egy banki tranzakció konstruktora vagy egy logolás, mint az én példám.
A legfontosabb dolog, hogy az AOP akkor lesz hasznos tagja az eszköztáradnak, amikor az OOP elvérzik valamilyen feladaton, ebben az esetben az átszövő vonatkozások kezelésén. Az AOP nem helyettesíti, hanem kiegészíti az OOP-t.
Én azt gondolom, hogy könnyen adódhat olyan szituáció, amikor szükségünk van az Aspektus-orientált programozásra.
Bibliográfia
(1) Danisovszky, M., Nagy, T., Répás, K., Kusper, G.: Western Canon of Software Engineering: The Abstract Principles, 2019
(2) Lengyel, L., Levendovszky, T.: Aspektus-orientált programozás, Híradástechnika Vol. \ LIX (2004/10, 8–12)