Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat autor RNDr. Ilja Kraval http://www.objects.cz Object Consulting s.r.o.
Obsah: Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat ....................1 1. Úloha k řešení ...................................................................................................2 2. Řešení pomocí vzoru STRATEGY ....................................................................3 3. Rozšíření řešení použitím vzoru DECORATOR resp. INTERPRET..................6
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
1. Úloha k řešení Při své konzultační a školící činnosti jsem měl možnost spolupracovat na projektech v mnoha firmách jak v ČR tak v SR. Mnohdy jsme narazili na opravdu zajímavá řešení, kdy se použily návrhové vzory (o vzorech viz například e-kniha Design Patterns v OOP na našem serveru zdarma). Tento článek pojednává o jednom takovém řešení. Nechť máme za úkol přijímat nějaký soubor s daty (třebas soubor převodních příkazů z banky) v nějakém formátu, například v textovém tvaru, případně ve formátu XML, anebo v jiném formátu (např. record, struktura apod.). Jednou z úloh je tzv. validace přijímaných dat, tj. ověření v tom smyslu, že pole, které je přijímáno, odpovídá svým typem a údaji nějakému pravidlu, které má být nad tímto polem dodrženo. Například víme, že první pole udává počet zpráv od 1 do 100. Druhé pole udává například datum založení dávky, další pole identifikátor dávky apod. Nad jednotlivými poli tedy bude spuštěno vždy nějaké pravidlo validace, které je schopno posoudit, zda právě načtené pole je z hlediska validace v pořádku anebo nikoliv. Úkolem je navrhnout způsob takové validace polí ve flexibilní podobě.
strana 2
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
2. Řešení pomocí vzoru STRATEGY V prvním přiblížení se vyřeší daný problém poměrně dosti jednoduše pomocí vzoru STRATEGY. Zaveďme abstraktní třídu, resp. ještě lépe interface, nazvěme jej například Validator. V něm zavedeme abstraktní metodu Validate se vstupním parametrem typu string, což reprezentuje pole pro validaci. Tato metoda má návratovou hodnotu typu boolean, což reprezentuje výsledek validace. Druhý alternativní způsob, jak předat výsledek validace než hodnotou boolean, je zavést vyvolání uživatelské výjimky v případě, že validace dopadla neúspěšně. Samozřejmě v tom případě je třeba uživatelské výjimky ošetřit. Pro každé pravidlo validace zavedeme jednoho potomka této abstraktní třídy (resp. potomka interfacu, případně implementujeme tento interface – záleží na vývojovém prostředí). Každý potomek přepíše tuto metodu podle svých potřeb validace. Jako příklad modelu tříd pro některé třídy validace s návratovou hodnotou viz následující diagram:
«interface» Validator
+ Validate(string) : boolean
ValidatorForDate
ValidatorForCountOfOrders
ValidatorForIdentifier
+ Validate(string) : boolean
+ Validate(string) : boolean
+ Validate(string) : boolean
obrázek 1 Zavedení interfacu Validator a jeho implementace do dědiců Jako další krok zavedeme správce těchto „validátorů“, nazvěme jej například ValidatorManager. Tento správce je zaveden jako kolekce s klíčem (např. Dictionary, Map, HashTable, Collection apod.). Klíč reprezentuje identifikátor dané validace a stává se dohodou „kdo je která validace“. Třída ValidatorManager je strana 3
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
zavedena jako jedno-instanční, tj. objekt z této třídy je pouze jeden, což vede k použití vzoru SINGLETON. Objekt správce validátorů obsahuje vždy po jednom z objektů z každého dědice v kombinaci s daným klíčem. Při inicializaci je přidáván každý validátor i s klíčem, například takto (pseudokód JAVACIS): ... MyValidator = new ValidatorForDate(); MyValidatorManager.Add(MyValidator, key=1); ...
Poté můžeme požádat správce o daný „validátor“ podle klíče, například takto (opět pseudokód): ... Validator MyValidator = MyValidatorManager.GetValidatorByKey(MyKey) ...
Této konstrukce poté využijeme při validaci a to tak, že ke každému příjmu dat přiřadíme instanční hodnoty udávající „ke kterému poli přísluší které pravidlo validace“. Nejjednodušší způsob jak toto provést, je přiřadit identifikátory validace vůči pořadí pole, například nějak takto: Pořadí pole
Id Validator (Key)
1
3
2
2
3
5
4
1
atd. Tyto hodnoty nechť jsou naplněny například do kolekce Keys a nechť pole jsou pro tento příklad zpřístupněny přes kolekci Fields. Při validaci polí, která vyvolává výjimky, se poté postupuje cyklem nějak takto (pseudokód):
strana 4
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
... ošetření výjimky: for i=1 to Fields.count { MyValidatorManager.GetValidatorByKey(Keys(i)).Validate(Fields(i)) } ...
Postupně se přebírají odpovídající pole a odpovídající klíče, převezme se daný validátor podle klíče (tj. „ani nevíme který“) od správce validátorů a spustí se metoda Validate() u dodaného validátoru.
strana 5
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
3. Rozšíření řešení použitím vzoru DECORATOR resp. INTERPRET Můžeme v úvahách pokračovat dále a ještě více rozšířit flexibilitu. V řešení, které jsme zatím uvedli, se pro každou jednu „strategii validace“ programuje určitý kód, tj. každé jedné strategii odpovídá jedna nějaká implementace kódu pro metodu Validate(). Představme si, že řešíme případ validace, kdy „pole je celé číslo rozsahu 1 až 100“. Asi se nebude jevit jako nejšťastnější řešení, pokud podmínku „1 až 100“ napíšeme v kódu „natvrdo“ nějak takto“: ... if (aField < 1) OR (aField > 100) then ... ...
Tento kód nemůže být opětovně použit pro další případy jiných rozsahů, protože hodnoty rozsahu nemají povahu měnitelných instancí. Jako lepší se jeví varianta zavést „obecnější“ třídu pro validaci rozsahu, která bude mít dva vnitřní členy m_Od a m_Do. Tyto členy musí být při inicializaci objektu validace vyplněny. Pseudokód části validace, kde se kontroluje rozsah, může potom vypadat nějak takto: ... if (aField < m_Od) OR (aField > m_Do) then ... ...
Tento kód je již možné opětovně použít pro různé hodnoty rozsahu. Je zřejmé, že součástí konfiguračních hodnot nejsou již pouze klíče, ale i hodnoty rozsahu. Pokud by například klíč pro validaci rozsahu celého čísla měl hodnotu 2, potom by konfigurační údaje mohly například vypadat nějak takto:
strana 6
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
Pořadí pole
Validator
1
key=3
2
key=2 od=1, do=100
3
key=5
4
key=1
5
key=2, od=1, do=4
atd. Další možný postup je nasnadě: Použijeme vzor DECORATOR. Názorně je to vidět na tomto příkladu s rozsahem. Co když nebudeme dávat dvě podmínky rozhodování (tj. „je menší“ a „je větší“) do jedné podmínky, ale zřetězíme je za sebou do dvou objektů? Nejprve se vyhodnotí první podmínka v prvním objektu a poté se zavolá následník v seznamu, čímž se vyhodnotí druhá podmínka. V tomto řešení je vlastně ukryto použití vzoru DECORATOR. Obecněji pojato vznikne tak zřetězený seznam objektů za sebou, každý z nich vyhodnotí „svou podmínku“ a zavolá objekt „za sebou“, např. nějak takto (pozn.: instance následujícího objektu se nazývá TheNext, metoda pro validaci „jeho části“ se nazývá Do(), jedná se samozřejmě o polymorfní metodu): Public void Do(); { ... if (aField < m_Od) then ... ... TheNext.Do(); }
Musíme nyní vyřešit dvě věci: Jednak je třeba ošetřit ukončení řetězu, což se dá provést buď vytvořením koncového prvku, který nedělá vůbec nic („konec cesty“), anebo každý volající objekt nejprve zjistí, zda má za sebou objekt a pokud nikoliv, nevolá jej. Druhý problém, který je třeba řešit, je inicializace objektů. Každý ze „strategy objektů“ validace se musí nejprve poskládat jako řetěz (například pouze jako jeden prvek „výkonný“ s jedním nečinným prvkem „konec cesty“ na konci řetězu) a teprve poté jej lze přidat do seznamu správce všech „strategy objektů“. Všimněme si, že v tomto případě se dokonce můžeme vyhnout polymorfismu na úrovni „strategy objektů“. Podobně jako „konec cesty“ můžeme zavést třídu „začátek cesty“. Objekty v seznamu všech „strategy objektů“ budou z této, tedy stejné třídy. Polymorfismus je
strana 7
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
poté schován až za hranicemi těchto objektů na úrovni prvků ze vzoru DECORATOR s metodou Do(). Ještě větší zobecnění lze dosáhnout použitím vzoru INTERPRETER, kdy dojde k libovolnému poskládání libovolných podmínek a výrazů (o tomto vzoru viz uvedená kniha), je však otázkou, zda by takovéto zobecnění bylo přínosem a zda by zbytečně nekomplikovalo řešení. V každém případě o všech možnostech zavedení flexibility a její míry začneme uvažovat tehdy, když nám hrozí „velmi častá změna“ těchto validačních pravidel anebo kdy si dokonce uživatel bude chtít tato validační pravidla sám určovat i v běhu programu, pracovat s nimi, měnit je, ukládat je apod. V tom případě se nabízí právě toto řešení „STRATEGY plus DECORATOR“. Nakonec ještě jedna důležitá poznámka. V rámci vyhodnocování algoritmu podmínky validace v prvcích DECORATOR nemusí být dané parametry algoritmů ve vnitřních členech pouze konkrétní hodnoty daných konfigurací. Může se například jednat o hodnoty převzaté z již existujících a žijících objektů v systému. Nechť například platí pravidlo, že každá přijímaná dávka příkazů musí mít identifikátor o jedna větší, než je počet přijatých dávek, ať už úspěšně anebo neúspěšně přijatých (žádná se nepřeskočila). Zde už s konfigurační konstantní hodnotou nevystačíme. Podmínka je potom vyjádřitelná například nějak takto (pozn.: objekt Batches_Of_Orders reprezentuje seznam přijatých dávek platebních příkazů): ... if (aField <> Batches_Of_Orders.Count + 1) then ... ...
Pokud je třeba i zde zavést flexibilitu, potom je možné tuto podmínku opět zobecnit a to použitím vzoru INTERPRETER, kdy se prvky podmínky „schovají“ za obecnější polymorfní metody. Například metoda Batches_Of_Orders.Count se nebude volat takto přímo, ale „schová se“ za nějakou polymorfní metodu a objekty s touto metodou budou opět vybírány ze seznamu podle klíče. Tím lze například toto pravidlo použít pro různé seznamy, nejenom pro seznam platebních příkazů, stačí jenom dosadit „ten správný seznam“. Podobně lze pomocí vzoru INTERPRETER zobecnit samotné pravidlo „s hodnotou plus 1“ na více flexibilní (například plus konfigurační konstanta jako vnitřní člen) . Je třeba však zdůraznit, že takto bychom postupovali pouze tehdy, když se flexibilita buď vyžaduje anebo bude očekávána. V opačném případě bychom určitě zůstali u jednodušší varianty jednotlivých algoritmů napsaných „natvrdo“ pro každou validaci každého prvku DECORATOR
strana 8
Použití vzorů STRATEGY a DECORATOR pro validaci přijímaných dat © ILJA KRAVAL, 2006, http://www.objects.cz
zvlášť, tj. použili bychom pouze kombinaci vzorů STRATEGY s DECORATOREM pro flexibilní výběr strategie validace pole s flexibilním řazením podmínek „za sebou“.
Konec článku Jakékoliv připomínky k článku pište prosím na adresu autora: mailto:
[email protected] Tento článek vyšel volně na serveru http://www.objects.cz, je dovoleno jej bezplatně šířit dále kopírováním beze změn
strana 9