VZÁJEMNÁ INTERAKCE NÁVRHOVÝCH VZORŮ A INSTANCIOVANÝCH OBJEKTŮ POMOCÍ METODY REFLEXE INTERACTION BETWEEN DESIGN PATTERNS AND INSTANTIATED OBJECTS BY REFLECTION Jaroslav Žáček Ostravská univerzita v Ostravě, 30. dubna 701 03 Ostrava, jaroslav.zacek@osu.cz Abstract This paper discusses interaction between well known design patterns and instantiated object by reflection methods. Reflection allows standalone class to instantiate another class by String name in running program. Before the reflection performer instantiate new class basic knowledge about class architecture must be provided. Class architecture depends on choosed and implemented design pattern. Information about design patterns can be saved directly to object using annotations. Keywords: reflection, design patterns, annotations, dynamic instantiation 1 Úvod V roce 2001 byl skupinou Object Management Group ustanoven nový přístup k vytváření aplikací a tento přístup byl nazván Model-Driven Architecture (MDA). Celý koncept je založen na doménově specifickém modelování. Aby byl tento model dostatečně využitelný pro praktickou tvorbu aplikací, jsou přístupné nástroje, většinou volně šiřitelné, které umožňují automatické přeložení modelu na kostru tříd. Tyto kostry tříd jsou poté základem pro kódování aplikace. Tímto přístupem je zachována i myšlenka architektury jako celku a budoucím vývojem na úrovni kódu je zachován původní návrh. MDA podporuje klíčovou vlastnost objektového přístupu a to znovupoužitelnost komponent. K dispozici jsou tak nástroje pro generování modelu z již fungující aplikace metodou reverzního inženýrství. Automatizované nástroje potřebné informace získávají většinou pomocí reflexe. Reflexe umožní načíst metamodel daného objektu, který reprezentuje již hotová třída. Lze zjistit počet konstruktorů, jejich argumenty vč. datových typů, metody zkoumané třídy vč. datových typů i samostatné názvy atributů a jejich hodnoty. U všech výše zmíněných lze navíc načíst a upravovat modifikátory. Reflexí lze tedy získat veškerá metadata, která tvoří třídu v daném jazyce a třída může být plnohodnotným obrazem v modelu. Přenesením modelu z konkrétní aplikace do modelu se ovšem ztratí možnost exekutivy, tj. okamžitého spuštění aplikace pro otestování dopadu menší změny architektury. Pro tento případ je nutno znovu automatizovaně přeložit upravený model do tříd jazyku dané platformy a až poté zkompilovat a otestovat funkčnost. Pro odstranění tohoto dodatečného kroku, který komplikuje automatizaci procesu vývoje lze použít také reflexi. Reflexe má možnost vytvářet instance objektů za běhu programu. Je nutno specifikovat konstruktor a jeho argumenty. Tyto informace lze vyčíst ze třídy samotné pomocí metod reflexe. Po instanciaci třídy je možno volat kteroukoliv metodu bez dalšího omezení daného jinou klíčovou vlastností objektového přístupu programování - zapouzdření. Přistoupit k atributům daného objektu bez instanciace má smysl tehdy, pokud jsou implicitně nastaveny a pro model je zajímavá jejich změna v důsledku instanciace. V roce 1995 byla vydána publikace Design Patterns. Elements of Reusable Object- Oriented Software a do podvědomí programátorů se dostaly návrhové vzory. Tyto vzory se staly jakousi povinnou součástí dobře navržené architektury aplikace, protože jsou odrazem 836
doporučených řešení nejčastějších problémů při vývoji aplikací. Proto je potřeba na ně v exekutivě dbát a správně s nimi zacházet z hlediska životního cyklu objektu. Tento příspěvek analyzuje možné problémy s exekutivou modelu při výskytu nejčastěji používaných návrhových vzorů. 2 Metody Pro testování návrhových vzorů je nejprve nutné vytvořit univerzální třídu, která je pomocí reflexe schopná instanciovat jiné třídy a uchovat instanci pro spuštění všech metod, které obsahuje. Z důvodu obecného testování je také nutno nadefinovat metody, které při zjištění typu argumentu spouštěné metody dokážou identifikovat tento typ jako objekt a vrátit jeho předdefinovanou implicitní hodnotu. Metoda počítá se základními datovými typy a polem. Všechny příklady jsou prezentovány v jazyce Java, protože je dobře vybaven pro práci s metadaty objektů a je silně typový. 2.1 Identifikace všech metod objektu Metoda uvedená na obr. 1 zajišťuje volání všech metod, které obsahuje třída. Toto volání je realizováno za běhu hlavní aplikace za použití předem vytvořené instance objektu dle zadaného jména - řetězce. Metoda je tedy schopna obsáhnout volání všech metod a u primitivních datových typů neztrácí na obecnosti. Obr. 1 Metoda zajišťující načtení všech metod a postupné volání za běhu programu 2.2 Instanciace objektu Aby mohla metoda uskutečnit volání, je nutno vytvořit instanci daného objektu a uchovávat její stav po dobu volání. Pro vytvoření instance je použita opět reflexe a výsledek tvorby instance se ukládá přímo do třídy jako atribut dané třídy. Na obr. 2 je znázorněno vytváření instancí objektu, jehož jméno je zadáno řetězcem za běhu programu. V metodě je pamatováno na případnou změnu modifikátoru, pokud je konstruktor nastaven jako soukromý. V návrhu se počítá s jednoduchými objekty o jednom konstruktoru, pokud by byl konstruktor přetížený, instanciovaný objekt by byl výsledkem volání prvního konstruktoru. Metoda by se musela také rozšířit o identifikaci argumentů, pokud by konstruktor nebyl bezparametrický. Tento návrh sice umí vytvořit novou instanci objektu, ovšem nedokáže brát ohledy na způsob instanciace a nedokáže ovlivnit počet instancí. Pokud je modifikátor uměle změněn pomocí metody,,setaccesible", vytváření instance nerespektuje původní návrh programátora dané třídy či komponenty. Tímto způsobem instanciace může dojít ke změně, či neinicializaci vnitřního stavu objektu a to má za následek špatnou funkčnost objektu jako takového. V této fázi nelze rozpoznat návrhový vzor, který byl použit při vytváření instanciovaného objektu. Pro některé návrhové vzory jsou typické jisté rysy, podle kterých lze s velkou pravděpodobností odvodit název vzoru a tím i instanciační přístup. Ovšem návrhové 837
vzory nejsou nikde standardizovány, jsou to pouze doporučené postupy na řešení konkrétního problému. Nelze tedy přesně říci, že pokud má třída například soukromý konstruktor a obsahuje statickou metodu getinstance() jedná se o návrhový vzor Jedináček - Singleton. Obr. 2 Tvorba instance objektu za běhu programu 3 Vybrané návrhové vzory Z předchozí kapitoly vyplývá, že při vytváření instancí i při následné práci s objekty v modelu nelze jednoznačně určit použitý návrhový vzor v návrhu architektury daného modelu. Proto je potřeba najít vhodný nástroj na rozšíření popisného modelu objektu - rozšíření metadat na úrovni objektu. Tento rozšířený popis bude jednoznačně identifikovat návrhový vzor a tím i definovat práci s objektem. Nejčastěji používané návrhové vzory rozděluje kniha Návrhové vzory do několika skupin. Mezi nejčastěji používané návrhové vzory pro řízení instancí lze zařadit Library class (knihovna tříd), Singleton (jedináček) a Pool (fond). Pro všechny tři zmíněné návrhové vzory je zapotřebí hlídat počet vytvářených instancí objektů. 3.1 Obalová třída První ze zmiňovaných slouží jako obalová třída pro statické metody, proto dle doporučení návrhového vzoru implementuje bezparametrický soukromý konstruktor a celá třída je navíc označena jako konečná (final). Proto nemá smysl vytvářet žádnou instanci daného objektu - instance by nijak nezměnila vnitřní stav. Tento návrhový vzor je označen jako Library Class - Knihovna tříd, v některé literatuře se lze setkat i s termínem Utility. Knihovna tříd jako obálka pro statické metody je použita např. v balíku java.lang.math, kde sdružuje často používané funkce nad primitivními číselnými typy. 3.2 Jedináček - Singleton Singleton je objekt, který je instanciován v rámci běhu aplikace právě jednou a na ostatní žádosti o vytvoření instance vrací již předem vytvořený objekt. Standardní implementace tohoto vzoru zajišťuje soukromý konstruktor a tovární metodu, která hlídá sama počet vytvořených instancí. Nebereme v úvahu fakt, že lze vytvořit pouze statický atribut a odkazovat se přímo na něj bez použití tovární metody, protože tento postup se dle literatury Návrhové vzory nedoporučuje. Na obr. 3 je znázorněn návrh třídy implementující návrhový vzor Singleton podle knihy Design Patterns, která je původním soupisem 23 návrhových vzorů. V tomto návrhu je použita tovární metoda pro získání instance a tato metoda byla nazvána GetSingletonData(), což je v rozporu s obecnými předpoklady z bodu 1.2. Proto je nutné rozšíření metadat třídy o popisnou část, která bude jednoznačně identifikovat metodu předávání vytvořené instance. 838
Obr. 3 Třída Singleton podle návrhu GoF 3.3 Fond - Pool Poslední zmiňovaný vzor je rozšíření návrhového vzoru Singleton. Počet instancí není omezen pouze na jednu, ale obecně na k-instancí. Při implementaci statické tovární metody se zajistí počet instancí a statický atribut uchovávající instance má charakter pole objektů. Pro tento návrhový vzor je potřeba uchovat ještě jednu informaci a to počet vytvořených instancí, neboli horní strop. Na základě tohoto čísla se omezuje počet vytvořených instancí, ovšem omezení vychází z vnitřní implementace dané třídy, parametr se předává do příslušné metody pro získání instance objektu. 4 Rozšíření metadat tříd Všechny výše popsané specifické přístupy k instanciaci lze ošetřit rozšířením metamodelu objektů implementujících návrhové vzory. Pro rozšíření metamodelu budou použity anotace. Anotace byly primárně určeny pro přidání meta informací do stávajícího. V současné době se anotace používají např. pro objektově-relační mapování Hibernate, kde jsou přímo do tříd EJB zapsány vztahy atributů třídy k databázovým relacím. Spojením reflexe a anotací lze přímo za běhu programu rozhodovat o postupu instanciace objektu či předávat potřebné parametry. 4.1 Zavedení anotací Anotace umožňují vytvoření vlastních rozhraní, které tvoří základ pro rozšíření metadat objektu. Syntézou výše popsaných charakteristik návrhových vzorů zaměřujících se na omezení počtu aktivních instancí bylo navrhnuto rozhraní na obr. 4. Obr. 4 Rozhraní pro rozhodování o instanciaci za běhu programu Toto rozhraní tak fakticky rozšiřuje metadata daného objektu, který ho implementuje. V tomto rozhraní jsou celkem tři atributy, které nejsou definovány implicitně a musí se nastavit v rámci každé implementace v objektu. Rozhraní v podobě anotace tak tvoří šablonu pro nutný popis metadat. Objekt je tak rozšířen o parametry designpattern, který určuje název návrhového vzoru, instancemethod, který udává název instanciační (tovární) metody a pool, který udává počet instancí. Parametry instancemethod a pool se předávají do metody jako datové typy String a Integer. Metoda poté pomocí reflexe zjistí metodu z objektu podle parametru instancemethod - String a jako argument pošle do metody parametr pool - Integer. Pro návrhový vzor Library Class tyto parametry nejsou využity, protože se instance nevytváří. Označení @Retention a @Target jsou označení pro vnitřní potřeby překladače. První direktiva naznačuje, že anotace je dostupná pro čtení za běhu programu - toto je důležité pro vlastní třídu realizující instanciaci. Druhá direktiva poté určuje navázání anotace na objekt. V 839
tomto případě je anotace použitelná pouze pro třídy a jiné rozhraní, eventuálně se dá nastavit použití na metodu či pole. Vlastní implementace rozhraní je znázorněna na obr. 5. Obr. 5 Implementace rozhraní - anotační zápis Tato anotace je přečtena reflexí za běhu programu a je jí ovlivněn způsob instanciace objektu. Pokud je objekt označen jako Library Class, jeho instance se nevytváří vůbec. Pokud se jedná o Singleton, je jeho instance vytvářena metodou definovanou v atributu instancemethod, v tomto konkrétním příkladu se jedná o metodu. Toto označení tovární metody vychází z praxe a je potvrzeno literaturou Návrhové vzory. Hodnota pool je nastavena u návrhového vzoru Singleton jako 1, ovšem tento parametr se z logiky věci nepředává do tovární metody, je přítomen pouze pro připomenutí. Pro návrhový vzor Pool se hodnota předává do tovární metody, která zabezpečuje implementaci samotného fondu a hlídá strop vložený parametrem. Tento parametr ovšem nemusí být povinný, existují implementace, kde je počet instancí dán konstantou třídy samotné. V těchto případech dokáže reflexe rozpoznat z metadat instanciovaného objektu, zda je argument pro tovární metodu vyžadován. 4.2 Zavedení anotací Na obr. 6 je znázorněno vytváření objektu pomocí nástroje reflexe a poskytnutého jména třídy. Objekt vykonávající instanci třídy dle jména si sám v sobě uchovává vytvořenou instanci, funguje tak jako obal nad vytvořenými instancemi a může vykonávat volání všech metod přímo. Obr. 6 Vytvoření instance třídy dle jména Nejprve třída vytvoří novou třídu pomocí metody Class.forName s parametrem jména třídy. V této chvíli se ještě nevytváří instance nové třídy, ale pouze se identifikuje odkaz na metadata třídy zadané jejím názvem za běhu programu. Jakmile je identifikována vazba k metadatům, lze načíst seznam všech anotací. Na rozdíl od zjištění jména třídy anotace nelze 840
zjistit přímým příkazem, ale je nutné si vyžádat seznam všech anotací. Tento seznam se poté iterativně prochází a kontroluje se původ anotace pomocí instanceof. Tento přístup je zvolen kvůli povaze anotací jakožto rozhraní. Příklad se zatím omezuje pouze na definici návrhových vzorů řetězci, pro budoucí použití a lepší standardizaci bude pravděpodobně nutno implementovat výčtový typ. Při identifikaci návrhového vzoru se předají potřebné parametry do metod zajišťujících instanciaci. 4.3 Instanciace Instanciační část zajišťují dvě metody. První metoda zajišťuje instanciaci standardního objektu, který nevyžaduje speciální tovární metodu, či jiným způsobem hlídat vytvořené instance. Tato metoda je uvedena na obr. 7. Obr. 7 Vytvoření instance standardního objektu Pro vytvoření instance se v knihovně reflexe nachází metoda newinstance. Ovšem před vytvořením instance je potřeba ještě zkontrolovat, jestli nejsou modifikátory konstruktoru nastaveny jako privátní a pokud jsou takto detekovány, je nutné je nastavit jako přístupné. Návrh je omezen pouze na jeden existující konstruktor, ostatní konstruktory nebere v úvahu a k tomuto jednomu konstruktoru přistupuje jako k bezparametrickému. Výsledek procesu instanciace se ukládá do vnitřní proměnné. Druhá instanciační metoda určená pro návrhové vzory požaduje dva vstupní parametry a to jméno metody a horní strop určující počet vytvořených instancí. Metoda je uvedena na obr. 8. Obr. 8 Vytvoření instance objektu, který limituje počet vytvářených instancí Zde je nejprve nutné získat odkaz na tovární metodu zajišťující vytvoření nové instance objektu. Odkaz se získá pomocí metody reflexe pro získání odkazu na metodu objektu dle jeho vnitřních metadat při zadání názvu metody. Při získání odkazu není jasné, jestli je metoda parametrická, či bez parametrů. Proto jsou implementovány metody pro zjištění argumentů odkazované metody. Návratová hodnota je pole typů. Typy jsou identifikovány třídou, ze které vychází jejich původní instance, resp. model metadat. Podle počtu parametrů se volá instanciační metoda zadaná typem String s parametrem, či bez parametrů. Metoda createinstancelimit je tímto univerzální pro oba návrhové vzory, Singleton i Pool. 841
5 Závěr Spojení reflexe a anotací skrývá velký potenciál pro tvorbu exekutivních modelů. Jejich popisnou část - rozšíření metamodelu objektů zajišťují anotace svou flexibilitou a možností uživatelských definic rozšířeného popisného modelu. Podařilo se identifikovat nejvíce používané návrhové vzory z hlediska kontroly vytvářených instancí. Tyto vzory jsme schopni označit anotacemi a poté je používat v exekutivním modelu. Počet instancí i jejich vytváření je v režii samostatného objektu, ovšem samotný přístup k instanciaci a uchování odkazů na objekt vykonává exekutivní model. 6 Literatura FORMAN, Ira R.; FORMAN, Nate. Java Reflection In Action. Mannig Publications Co., 2005, 273 s. ISBN 1-932394-18-4 GAMMA, Erich; HELM, Richard; JOHNSON, Ralph; VLISSIDES, John. Design Patterns. Elements of Reusable Object-Oriented Software. Addison-Wesley, 1995, 396 s. ISBN 0-201-30998-X. PECINOVSKÝ, Rudolf. Návrhové vzory. Computer Press, 2007, 527 s., ISBN 978-80-251-1582-4 Recenzent: doc. Ing. František Huňka, CSc, Ostravská univerzita v Ostravě 30. dubna 701 03 Ostrava, e-mail: frantisek.hunka@osu.cz 842