Korábban már írtma arról, hogy elkezdtem egy nagyobb lélegzetvételű irományt. Híreket ugyan nem hoztanfelőle, hanem egyből egy részletet. Nem csak ennyi késült el belőle, de ez az első, ami már tartalmaz mindent. Probléma leírást, elmélkedést arról, hogyan is kellene megoldani és persze a megoldás példával. Imhol
Query Object és Interpreter
(http://martinfowler.com/eaaCatalog/queryObject.html)
Miért is van szükségünk? Bizonyos feltételek alapján szeretnénk adatokat kinyerni. Az SQL felétetele sokszor nem jöhet szóba.
Miért is?
Alapvető, hogy nem mindenki ért az SQL-hez. Másrészről amúgy sem jó, ha a kliens kódból engedélyezzük a SQL direkt használatát. De vegyük azt is, hogy nem mindig relációs adatbázis az adatforrásunk.
Megoldás lehet, hogy annyi variációt készítünk a lekérdező eljárásból, amennyi lehetséges lekérdezési variáció lehetséges. De ez sem a legjobb megoldás, hiszen ez is korlátot szab nekünk. De mi van akkor, ha nem akarunk ezzel a limitációval együtt élni? És akkor, ha a lekérdezés dinamikusan készül, mondjuk a felhasználó mindenféle kattintgatással, egérhúzogatással állítja össze a lekérdezést? Igen-igen, ez nem csak adatbázist kotorászó rendszereknél működik, hanem akár készletnyilvántartónál is. Igaz itt nem azt fogja összeállítani, hogy a KSZLT tábla NM mezője, hanem valami olyasmit, hogy Készlet->Name (ez a példa persze csak szöveges reprezentációja a tényleges objektumoknak).
Ok! Szépen felépítjük a Query Objectumot majd miután átkerült a célrendszerbe (gatewayen keresztül) ott a rendszer sajátos igénye alapján feldolgozzuk, azaz interpretáljuk, mint egy sajátos szintaxisú nyelvet. Ha adatbázisból kérdezünk, akkor megfelelő SQL lekérdezéssé alakul, de ha valami más, akkor „kézzel” interpretáljuk a filtert.
A fentiek alapján egyértelmű, hogy egy QO feldolgozása az Interpreter tervezési minta egy megvalósítása. Könnyen arra az elhatározásra is juthat az ember, hogy ha különböző rendszerekben, implementációkban használjunk egy az egyikben sem jelen lévő interfészt akkor akár Adapter is lehet de nem így van. Igaz, hogy különféle külső rendszert egy új, közös interfész mögé tesszük, de itt nem a külső szolgáltatást használjuk, hanem az használja a mi objektumainkat. Kicsit fordított a helyzet. Persze ha arról lenne szó, hogy egy adott SQL dialektusba alakítjuk, akkor már szóba jöhet az adapter is, de csak az interpreteren belül.
Az alábbiakban egy egyszerű implementációt mutatok be. Sajátosan nem a szótárprogram keretein belül, mert ott nem használtam ilyen megoldást. Az implementációs nyelv a Python (ahány nyelvet beszélsz annyi ember vagy, ez igaz a programozási nyelvekre is, minden nyelv tágítja a szakmai nézőpontot).
Kezdjük a domain objectumainkkal és a többivel. Ahol kellhet adok magyarázatot.
""" create table Task( task_id integer, task_name varchar(256), task_desc clob, task_due date, task_finished char(1) ); """ #Domain Objects class Task: def __init__(self,id,name,description,dueDate,finished): self._id = id self._name=name self._description=description self._dueDate=dueDate self._finished=finished def id(self): return self._id def name(self): return self._name def description(self): return self._description def dueDate(self): return self._dueDate def finished(self): return self._finished def setId(self,id): self._id = id def setName(self,name): self._name = name def setDescription(self,description): self._description = description def setDueDate(self,dueDate): self._dueDate = dueDate def setFinished(self,finished): self._finished = finished # Query Objects class Where: def __init__(self, condition): self._cond = condition def condition(self): return self._cond class Comparison: def __init__(self,left,right): self._left = left self._right = right def left(self): return self._left def right(self): return self._right class Equal(Comparison): pass class GreaterThen(Comparison): pass class LessThen(Comparison): pass class And(Comparison): pass class Or(Comparison): pass
class Constant: def __init__(self, value): self._value = value def value(self): return self._value class FieldDef: def __init__(self,className,attribute): self._className = className self._attr = attribute def className(self): return self._className def attribute(self): return self._attr
fregParsers = { 'Where': whereFreg ,'Equal': equalFreg ,'FieldDef': fieldDefFreg ,'GreaterThen': greaterThenFreg ,'LessThen':lessThenFreg ,'Constant': constantFreg ,'And': andFreg ,'Or': orFreg } #Interpreters def sqlFregment(freg): className = freg.__class__.__name__ f = fregParsers[className] return f(freg) def whereFreg(where): s = 'where ' s+=sqlFregment(where.condition()) return s def equalFreg(equal): s = '(' s+=sqlFregment(equal.left()) s+='=' s+=sqlFregment(equal.right()) s+=')' return s def greaterThenFreg(gthen): s = '(' s+=sqlFregment(gthen.left()) s+='>' s+=sqlFregment(gthen.right()) s+=')' return s def lessThenFreg(lthen): s = '(' s+=sqlFregment(lthen.left()) s+='<' s+=sqlFregment(lthen.right()) s+=')' return s def andFreg(lthen): s = '(' s+=sqlFregment(lthen.left()) s+=' and ' s+=sqlFregment(lthen.right()) s+=')' return s def orFreg(lthen): s = '(' s+=sqlFregment(lthen.left()) s+=' or ' s+=sqlFregment(lthen.right()) s+=')' return s def constantFreg(const): if const.value().__class__.__name__ == 'str': return "'"+const.value()+"'" return str(const.value())
attr2field = { 'Task.id':'task_id' ,'Task.name':'task_name' ,'Task.description':'task_desc' ,'Task.dueDate':'task_due' ,'Task.finished':'task_finished' } def fieldDefFreg(fieldDef): return attr2field[fieldDef.className()+'.'+fieldDef.attribute()] import unittest class TestQa(unittest.TestCase): def testFieldDefFreg(self): self.assertEqual('task_id',fieldDefFreg(FieldDef('Task','id'))) self.assertEqual('task_name',fieldDefFreg(FieldDef('Task','name'))) self.assertEqual('task_desc',fieldDefFreg(FieldDef('Task','description'))) self.assertEqual('task_due',fieldDefFreg(FieldDef('Task','dueDate'))) self.assertEqual('task_finished',fieldDefFreg(FieldDef('Task','finished'))) def testFieldDefFregFail(self): try: fieldDefFreg(FieldDef('Project','decription')) self.fail() except: pass def testConstant(self): self.assertEqual ('1',constantFreg(Constant(1))) def testEqual(self): self.assertEqual('(2=1)',equalFreg(Equal(Constant(2),Constant(1)))) def testGreaterThen(self): self.assertEqual('(2>1)',greaterThenFreg(GreaterThen(Constant(2),Constant(1)))) def testLessThen(self): self.assertEqual('(2<1)',lessThenFreg(LessThen(Constant(2),Constant(1)))) def testComplex(self): self.assertEqual( "where ((task_id>1) and (task_name='BORGR'))", sqlFregment(Where(And(GreaterThen(FieldDef('Task','id'),Constant(1)),Equal(FieldDef('Task','name'),Constant("BORGR"))))) ) if __name__ == "__main__": print 'hello' unittest.main()