+ All Categories
Home > Documents > APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace...

APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace...

Date post: 18-Jul-2020
Category:
Upload: others
View: 12 times
Download: 0 times
Share this document with a friend
155
APLIKACE NÁVRHOVÝCH VZORŮ URČENO PRO VZDĚ LÁVÁNÍ V AKREDITOVANÝCH STUDIJNÍCH PROGRAMECH FRANTIŠEK HU Ň KA ČÍSLO OPERAČNÍHO PROGRAMU: CZ.1.07 NÁZEV OPERAČNÍHO PROGRAMU: VZDĚLÁVÁNÍ PRO KONKURENCESCHOPNOST OPATŘENÍ: 7.2 ČÍSLO OBLASTI PODPORY: 7.2.2 INOVACE VÝUKY INFORMATICKÝCH P ŘEDMĚT Ů VE STUDIJNÍCH PROGRAMECH OSTRAVSKÉ UNIVERZITY REGISTRAČČÍSLO PROJEKTU: CZ.1.07/2.2.00/28.0245 OSTRAVA 2012
Transcript
Page 1: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

APLIKACE NÁVRHOVÝCH VZOR Ů

URČENO PRO VZDĚLÁVÁNÍ V AKREDITOVANÝCH STUDIJNÍCH PROGRAMECH

FRANTIŠEK HU ŇKA

ČÍSLO OPERAČNÍHO PROGRAMU: CZ.1.07 NÁZEV OPERAČNÍHO PROGRAMU:

VZDĚLÁVÁNÍ PRO KONKURENCESCHOPNOST OPATŘENÍ: 7.2

ČÍSLO OBLASTI PODPORY: 7.2.2

INOVACE VÝUKY INFORMATICKÝCH P ŘEDMĚTŮ VE STUDIJNÍCH PROGRAMECH OSTRAVSKÉ UNIVERZITY

REGISTRAČNÍ ČÍSLO PROJEKTU: CZ.1.07/2.2.00/28.0245

OSTRAVA 2012

Page 2: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

Tento projekt je spolufinancován Evropským sociálním fondem a státním rozpočtem České republiky Recenzent: RNDr. Jaroslav Žáček, Ph.D. Název: Aplikace návrhových vzorů Autor: doc. Ing. František Huňka, CSc. Vydání: první, 2012 Počet stran: 154 Jazyková korektura nebyla provedena, za jazykovou stránku odpovídá autor. © doc. Ing. František Huňka, CSc. © Ostravská univerzita v Ostravě

Page 3: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

3

OBSAH Úvod pro práci s textem pro distanční studium ............................... 4

1 Úvod do problematiky .............................................................. 5

2 Vzor Strategie a Šablona .......................................................... 9 2.1 Strategie ....................................................................................... 9 2.2 Šablona ....................................................................................... 13

3 Vzor Model-View- Controller (Observer) ............................. 22

4 Vzory Dekorátor a Visitor ..................................................... 34 4.1 Dekorátor ................................................................................... 34 4.2 Visitor ........................................................................................ 39

5 Vzor Továrna (Factory) .......................................................... 43

5.1 Jednotuchá továrna (Simple Factory) ........................................ 43 5.2 Tovární metoda (Factory Method) ............................................. 47 5.3 Abstraktní továrna (Abstract Factory) ....................................... 50

6 Vzor Příkaz (Command) ......................................................... 55

7 Vzory Adaptér a Fasáda (Adapter, Facada ) ........................ 66

7.1 Vzor Adaptér .............................................................................. 66 7.2 Vzor Fasáda ............................................................................... 72

8 Vzory Iterátor a Skladba (Iterator, Composite) ................... 79 8.1 Vzor Iterátor ............................................................................... 79 8.2 Vzor Skladba .............................................................................. 91

9 Vzor Stav (State) ..................................................................... 97

10 Vzor Proxy .............................................................................. 105 10.1 Vzdálené proxy ........................................................................ 109 10.2 Dynamické proxy ..................................................................... 114

11 Vzory architektury ................................................................ 121 11.1 Vzor vrstvy (Layers) ................................................................ 121 11.2 Vzor roury a filtry (Pipes and Filters) ...................................... 127

12 Návrhový vzor Broker .......................................................... 133

13 Adaptivní systémy .................................................................. 141 13.1 Microkernel .............................................................................. 141 13.2 Reflexe (Reflection) ................................................................. 149

Korespondenční úkoly ................................................................... 154

Literatura ........................................................................................ 155

Page 4: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

4

Úvod pro práci s textem pro distanční studium

Cíl předmětu

Seznámit studenty s praktickým využitím návrhových vzorů. Popis a vysvětlení jednotlivých návrhových vzorů je ještě doplněno názornými příklady v jazyce Java. Tento aspekt výuky je velmi zásadní a umožňuje lépe pochopit vlastní podstatu návrhových vzorů.

Po prostudování textu budete znát:

Tento učební text je určen studentům informatiky pro předmět Aplikace návrhových vzorů. Cílem textu je seznámit studenty se zavedenými a opětovně použitelnými vzory v objektově orientovaném přístupu. Studium jednotlivých vzorů také pomůže nejen prohloubit chápání principů OOP, ale také při jejich praktickém uplatnění v navrhovaných aplikacích. Záverečné kapitoly textu se věnují vzorům architektury.

V textu jsou dodržena následující pravidla:

- je specifikován cíl kapitoly (tedy co by měl student po jejím absolvování umět, znát, pochopit),

- výklad učiva,

- důležité pojmy,

- doplňující otázky a úkoly k textu.

Úkoly

Na konci učebního textu jsou uvedeny tři příklady, které během semestru zpracujete a zašlete ke kontrole vyučujícímu. Zadané příklady odpovídají postupně probíranému učivu. Podrobné informace obdržíte na úvodním tutoriálu.

Pokud máte jakékoliv věcné nebo formální připomínky k textu,

kontaktujte autora ([email protected]). Inovace předloženého textu

Učební text prošel celkovou úpravou. Kompletně byly přepracovány nebo nově doplněny následující návrhové vzory: Jednoduchá továrna, Tovární metoda, Abstraktní továrna, Adaptér, Fasáda, Iterátor, Skladba, Stav, Vzdálené proxy, Dynamické proxy. Z původního textu byly vypuštěny návrhové vzory Forwarder-Receiver a Client-Dispatcher-Server.

Page 5: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

5

1 Úvod do problematiky

V této kapitole se dozvíte:

• co jsou návrhové vzory a jak se používají,

• jaké jsou tři základní aspekty každého vzoru,

• jak se návrhové vzory dokumentují.

Po jejím prostudování budete schopni:

• lépe rozumět důvodům, proč se používají návrhové vzory a jaké

může být jejich potenciální využití při tvorbě aplikací.

Klí čová slova této kapitoly:

návrhový vzor, kontext, problém, řešení

Základní pojmy Jednou z možností tvorby objektově orientovaných aplikací je využití tzv. návrhových vzorů resp. vzorů pro návrh angl. Design Patterns. Klíčovým pojmem pro vysvětlení vzorů pro návrh je znovupoužitelnost. Znovupoužitelnost v oblasti tvorby software znamenala úspěch především v objektově orientovaných jazycích – tam je označována jako dědičnost – a také v oblasti business objects. Business objects jsou části software, které je možno po malém přizpůsobení použít v typizované oblasti (např. účetnictví). Obě tato použití mají společnou vlastnost – znovupoužitelnost je v nich použita na úrovni implementace. Vzory pro návrh vycházejí z práce zkušeného návrháře – ten totiž nezačíná svoji práci od nuly, ale pokud se nějaký způsob řešení osvědčil, má tendenci používat jej v dalších projektech, čímž jej zdokonaluje. Způsob řešení zde neznamená použití určité části kódu, ale znovupoužití znalostí nabytých v průběhu návrhu. Znovupoužitelnost je tedy možno chápat ve více úrovních. Pokud mluvíme o dědičnosti a business objektech, jde o znovupoužitelnost na úrovni implementační, pokud mluvíme o návrhových vzorech, jde o znovupoužitelnost v oblasti návrhu. Co jsou vzory pro návrh. Návrhové vzory by měly napomoci účinnému sdílení a znovupoužitelnosti znalostí na vyšší úrovni mezi lidmi zabývajícími se

Page 6: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

6

projektováním a návrhem v určitém oboru. Návrhové vzory mají svůj původ v architektuře, kde byly použity pro popis opakujících se námětů při navrhování architektonických celků. Odtud se rozšířily nejen do oblasti objektově orientovaného návrhu, ale i do dalších oborů. Vzorem pro návrh (v softwarové oblasti) může být libovolná znalost, která vznikla při návrhu informačního systému abstrakcí od specifických konkrétních podmínek. Je zřejmé, že taková znalost musí proto, aby mohla být uložena a znovupoužita dostat konkrétní fyzickou podobu. Forma zápisu vzoru pro návrh není pevně dána, ale je závislá na tom, čeho se zkušenost, kterou chceme vyjádřit návrhovým vzorem, týká. Pokud jde např. o zobecnění vztahů mezi objekty a definici rolí, je možné použít objektový diagram, pokud jde o zobecněné chování, může se jednat o sekvenční diagram atd. Z praktické zkušenosti vyplývá, že každý model použitý při návrhu potřebuje slovní komentář, který bude přesně specifikovat použité prvky a vazby v modelu a umožní nezúčastněnému čtenáři pochopení problému. Pro objasnění účelu, podmínek použití, struktury atd. se hodí např. níže uvedený popis. Atributy pot řebné pro popis vzoru Jméno vzoru resp. další jména, pod kterými je vzor znám. Kontext – situace, ve které se může vzor uplatnit. Problém – je problém, který vzor řeší včetně diskuse požadavků. Řešení – základní princip řešení. Struktura – detailní specifikace struktury vzoru a jejich aspektů. Dynamika – je popsána scénářem chování vzoru během jeho vykonávání. Implementace – hlavní body implementace vzoru. Varianty řešení. Známé použití vzoru. Výhody navrženého řešení. Slabá místa řešení. V poslední době se návrhové vzory přímo spojují se softwarovou architekturou. Rozsah návrhových vzorů může pokrývat nejvyšší úroveň tzv. vzory architektury přes návrhové vzory až po vzory na nejnižší úrovni tzv. idiomy. Často se vyskytuje v této souvislosti pojem vzorově orientovaná softwarová architektura (pattern-oriented software architecture). Návrhové vzory mohou být klasifikovány buď podle účelu na vzory pro tvorbu objektů, vzory pro řešení struktury, vzory řešící chování, nebo podle kritéria granularity na vzory architektury, vzory pro návrh a idiomy.

Charakteristika vzoru

Vzor můžeme výstižně charakterizovat jako vztah (relace) mezi daným kontextem, problém a řešením.

Page 7: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

7

Každý prvek na světě, každý vzor je vztahem mezi jistým kontextem, daným systémem požadavků (sil), které nastávají opakovaně v tomto kontextu a danou prostorovou konfigurací, která dovoluje těmto silám vyřešit své působení. Kontext: situace, která způsobuje problém. Problém: opakující se problém v daném kontextu. Řešení: prověřené a ověřené řešení problému. Kontext: Kontext rozšiřuje stručný popis o popis situace, ve které problém vzniká. Kontext vzoru může být zcela všeobecný, např. „vytvoření software s rozhraním pro člověka“. Na druhé straně kontext může spolu vázat specifické vzory jako např. implementace mechanismu propagace změny ve vzoru Model View Controller. Specifikace správného kontextu je pro vzor obtížná. Prakticky je nemožné určit všechny situace, jak všeobecné, tak specifické, ve kterých může být vzor použit. Daleko pragmatičtější je mít v seznamu všechny známé situace, kde problém, který je řešen daným vzorem, se může vyskytnout. To samozřejmě negarantuje, že pokryjeme všechny situace, ve kterých může být vzor relevantní, ale alespoň to dá cenné vodítko. Problém: Tato část schématu popisu vzoru popisuje problém, který vzniká opakovaně v daném kontextu. Začíná s obecnou specifikací problému, zaměřujíce se na to nejpodstatnější co musí návrhový vzor řešit. Například vzor Model-View-Controller je zaměřen na problémy způsobené změnou uživatelského rozhraní. Tento problém specifikuje dva protichůdné požadavky:

• uživatelské rozhraní by mělo být snadno modifikovatelné;

• funkcionální jádro programového řešení by nemělo být ovlivněno touto modifikací.

Požadavky obecně pomáhají prodiskutovat problém z různých úhlů pohledu a pomáhají porozumět detailům. Mohou působit stejným směrem, nebo proti sobě. Dva protichůdné požadavky jsou např. rozšiřitelnost systému versus minimalizace velikosti jeho jádra. Když chcete, aby váš systém byl rozšiřitelný, máte tendenci použít abstraktní nadtřídu. Když ale chcete minimalizovat rozsah kódu, nemůžete si dovolit luxus, jako jsou abstraktní nadtřídy. Nejdůležitější jsou samozřejmě klíčové požadavky řešící problém. Řešení: Řešení je část popisu vzoru, která ukazuje, jak vyřešit opakující se problém, nebo lépe jak vyrovnat působící požadavky (síly) spojené s tímto problémem. V softwarové architektuře má takové řešení dva aspekty.

Page 8: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

Za prvé, každý vzor určuje (specifikuje) jistou strukturu (prostorovou konfiguraci prvků). Tato struktura má vazbu na statické aspekty a jako každá jiná softwarová architekturavztahů mezi nimi. Uvnitř této struktury slouží komponenty jako stavební bloky a každá komponenta má svoji funkci (zodpovVztahy mezi komponentami ur Za druhé, každý vzor je určchování má vazbu na dynamické aspekty vzoru spolupracují, jak je mezi nimi organizovaná prácekomunikují mezi sebou. Je důležité zdůraznit, že řešení nemusí nevyhnutnpožadavky spojené s daným problémem. Mz nich a zbytek požadavků nechat nepožadavky působí protichůdnvyřešení. Návrhový vzor se využívá přZákladní charakteristikou každého vzoru je Situace, kterou je třeba řešit je vždy vkteré se vyskytují, bývají protichrovnováhy mezi různými požadavky. Vvšechny požadavky.

Kontrolní otázky

1. Jak lze charakterizovat návrhový vzor

2. V čem se liší znovupoužitelnost zorientovaného přístupu od znovupoužitelnosti návrhových vzorů?

Shrnutí obsahu kapitoly

Cílem úvodní kapitoly je popsatbude věnovat a tím studenty motivovat ke studiu

8

čuje (specifikuje) jistou strukturu (prostorovou ). Tato struktura má vazbu na statické aspekty řešení

dá jiná softwarová architektura se skládá z komponent a mezi nimi. Uvnitř této struktury slouží komponenty jako

stavební bloky a každá komponenta má svoji funkci (zodpovědnost). Vztahy mezi komponentami určují jejich umístění.

je určen chováním za běhu (při použití). Toto zbu na dynamické aspekty řešení, tedy jak dílčí části

ak je mezi nimi organizovaná práce, jak

řešení nemusí nevyhnutně vyřešit všechny daným problémem. Může se soustředit na některé

ů nechat neřešen. Musíme mít na mysli, že ůdně, což mnohdy komplikuje jejich úplné

Návrhový vzor se využívá při znovupoužitelnosti na úrovni znalostí. Základní charakteristikou každého vzoru je kontext, problém a řešení.

řešit je vždy v nějakém kontextu. Požadavky, bývají protichůdné. Řešení obsahuje vytvoření ými požadavky. V řešení nemusí být uspokojeny

charakterizovat návrhový vzor?

em se liší znovupoužitelnost z hlediska objektově řístupu od znovupoužitelnosti návrhových

Cílem úvodní kapitoly je popsat problematiku, které se učební text novat a tím studenty motivovat ke studiu učebního textu.

Page 9: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

2 Vzor Strategie a Šablona (Strategy, Template)

V této kapitole se dozvíte:

• jak je možné využít polymorfismus v

vzorech,

• jak je možné dodeklarovat generické metody,

• jak se dají využívat tzv.

Po jejím prostudování budete schopni:

• lépe chápat

• využít získané v

Klí čová slova této kapitoly:

polymorfismus, strategie, šablona

2.1 Vzor Strategie

Kontext: V různých oblastech mmůžeme řpožadováno, aby konkrétní algoritmus (strategie) byl vybrán až vběhu programu. podle algoritmConjugateGradient Problém:Je třeba, aby aplikace dovedla pracovat sdaný problém. dostatečně Řešení: Záměrem vzoru Strategie je zapouzdstrategie, čkde každá implementuje danou operaci (strategii). pak možné spotoho se rozhodnout.

Vzor Strategie a Šablona (Strategy, Template)

této kapitole se dozvíte:

jak je možné využít polymorfismus v jednotlivých návrhových

vzorech,

jak je možné dodeklarovat generické metody,

jak se dají využívat tzv. operace orientované na

Po jejím prostudování budete schopni:

lépe chápat a využívat polymorfismus,

využít získané vědomosti při aplikacích návrhových vzor

ová slova této kapitoly:

polymorfismus, strategie, šablona.

2.1 Vzor Strategie

zných oblastech můžeme narazit na problém, že danou operaci

žeme řešit několika možnými algoritmy (strategiemi). Navíc je požadováno, aby konkrétní algoritmus (strategie) byl vybrán až v

hu programu. Konkrétně se může jednat o vyhledání minimapodle algoritmů: LeastSquare, NewtonsMethod, Bisection ConjugateGradient.

Problém: eba, aby aplikace dovedla pracovat s různými algoritmy,

daný problém. Řešení pomocí přepínače není vhodné, protože není čně přizpůsobitelné změnám (flexibilní).

ěrem vzoru Strategie je zapouzdřit alternativní přístupy (nebo , čtyři metody uvedené v kontextu) v samostatných t

kde každá implementuje danou operaci (strategii). Za bpak možné spočítat minimum různými metodami (strategiemi) a podle toho se rozhodnout.

9

ednotlivých návrhových

orientované na vzory.

návrhových vzorů.

žeme narazit na problém, že danou operaci kolika možnými algoritmy (strategiemi). Navíc je

požadováno, aby konkrétní algoritmus (strategie) byl vybrán až v době že jednat o vyhledání minima, a to

LeastSquare, NewtonsMethod, Bisection a

znými algoritmy, řešící e není vhodné, protože není

it alternativní přístupy (nebo samostatných třídách,

Za běhu programu je znými metodami (strategiemi) a podle

Page 10: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

10

Obr. 2.1 Diagram tříd UML zobrazující strukturu návrhového vzoru Strategie Třída MiniSolver obsahuje jednak odkaz prostřednictvím datového atributu strategy na rozhraní FindMinima. Dále metody minima(), která realizuje konkrétní algoritmus a metoda changeAlgoritmus(), která mění algoritmus výpočtu – viz zdrojový výpis programu. public interface FindMinima { // Line is a sequence of points: double[] algorithm(double[] line); }

Rozhraní FindMinima pak implementuje řada různých metod, které mají svůj algoritmus pro výpočet minima. Pro snadné pochopení vzoru algoritmy (strategie) nemají „těla“ metod, ale pouze vypisují řadu reálných čísel jako výsledek algoritmu. // Různé strategies výpočtu minima: public class LeastSquares implements FindMinima { @Override public double[] algorithm(double[] line) { return new double[] { 1.1, 2.2 }; } } public class NewtonsMethod implements FindMinima { @Override public double[] algorithm(double[] line) { return new double[] { 3.3, 4.4 }; } }

Page 11: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

11

public class Bisection implements FindMinima { @Override public double[] algorithm(double[] line) { return new double[] { 5.5, 6.6 }; } } public class ConjugateGradient implements FindMinima { @Override public double[] algorithm(double[] line) { return new double[] { 3.3, 4.4 }; } }

Třída MinimaSolver deklaruje datový atribut typu rozhraní FindMinima strategy, do kterého je pak uložen konkrétní algoritmus podle kterého má probíhat výpočet. Konstruktor této třídy zabezpečí, aby do datového atributu byla přiřazena konkrétní hodnota. Metoda minima() pak provádí konkrétní výpočet podle zadaného algoritmu (strategie). Další metoda changeAlgorithm() dovoluje změnit algoritmus (strategii) za jiný. public class MinimaSolver { private FindMinima strategy; public MinimaSolver(FindMinima strat) { strategy = strat; } double[] minima(double[] line) { return strategy.algorithm(line); } void changeAlgorithm(FindMinima newAlgorithm) { strategy = newAlgorithm; } }

Třída ArrayX je pomocná třída, která je využita pro převod pole reálných čísel na řetězec, který se pak snadno vytiskne. Vlastní převodní metoda je třídní (statická), a proto nemá ani třída z vnějšku přístupný konstruktor. Pro převod je využita třída StringBuffer. public class ArrayX { private ArrayX() {} public static String toString(double[] a) { StringBuffer result = new StringBuffer("["); for(int i = 0; i < a.length; i++) { result.append(a[i]); if(i < a.length - 1) result.append(", "); } result.append("]"); return result.toString(); } }

Page 12: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

12

Třída StrategyPattern pak demonstruje využití vzoru. Proměnná solver je instancí třídy MinimaSolver a při jejím vytvoření se jako parametr předává konkrétní algoritmus (strategie) výpočtu. Pole line obsahuje řadu reálných čísel, sloužící jako fiktivní vstup do algoritmu výpočtu. Výraz solner.minima(line) vrací výsledek (fiktivní) použitého algoritmu. Metoda solver.changeAlgorithm() mění algoritmus (strategii) výpočtu. public class StrategyPattern { public static void main(String args[]) { MinimaSolver solver = new MinimaSolver(new LeastSquares()); double[] line = { 1.0, 2.0, 1.0, 2.0, -1.0, 3.0, 4.0, 5.0, 4.0 }; System.out.println( ArrayX.toString(solver.minima(line))); solver.changeAlgorithm(new Bisection()); System.out.println( ArrayX.toString(solver.minima(line))); } }

Návrhový vzor Strategy zjednodušuje složitou strukturu výpočtu tím, že zavádí pro každou strategii samostatnou třídu. Každá takováto třída zapouzdřuje jednu strategii (algoritmus výpočtu) a vytváří jednodušší kód. Typický klient má k dispozici výběr strategie. Je k tomu využito polymorfismu.

Page 13: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

2.2 Šablona

Problém:Při definování meto(návrh) algoritmu a ponechali možnost rozdílné implementace jistých kroků. Kontext: Definice metod, které mají spolev implementaci konkrétních krok Řešení: Zavedení vzoru Šimplementovat daný algoritmus vněkterých krok(dodefinovat). Nejdříve uvedeme ppřípravy kávy a Příprava kávy a public class final

} public

} public void

System. } public } public

System. } } Metoda prepareRecipe()metody jsou uvedeny následnje podobná, má však d

2.2 Šablona – Template Method

Problém: i definování metody bychom rádi definovali pouze základní obrysy

(návrh) algoritmu a ponechali možnost rozdílné implementace jistých

Definice metod, které mají společný obecný základ, ale liší se

implementaci konkrétních kroků.

Zavedení vzoru Šablona (Template method), jejímžimplementovat daný algoritmus v metodě, přičemž odkládá definování

kterých kroků algoritmu tak, že je jiné třídy mohou redefinovat (dodefinovat).

říve uvedeme příklad objasňující vzor šablona na ppravy kávy a čaje. Dále pak následuje klasický příklad t

íprava kávy a čaje – zápis v kódu

class Coffee {

final void prepareRecipe() { boilWater(); // vaření vody brew(); // spaření pourInCup(); //nalévání do šálku addSugarAndMilk(); //přidání cukru a mléka

public void boilWater() { System.out.println("Vaření vody");

public void void brew() { System.out.println("Překapání kávy přes filtr"

public void pourInCup() { System.out.println("Nalití kávy do šálku"

public void addSugarAndMilk() { System.out.println("Přidání cukru a mléka"

prepareRecipe() – obsahuje celý postup přípravy kávymetody jsou uvedeny následně. Metoda prepareRecipe()je podobná, má však dvě různé metody.

13

dy bychom rádi definovali pouze základní obrysy (návrh) algoritmu a ponechali možnost rozdílné implementace jistých

ný obecný základ, ale liší se

jejímž cílem je emž odkládá definování ídy mohou redefinovat

ující vzor šablona na příkladu říklad třídění.

//přidání cukru a mléka

"Překapání kávy přes filtr");

"Nalití kávy do šálku");

"Přidání cukru a mléka");

řípravy kávy. Dílčí prepareRecipe() pro třídu Tea

Page 14: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

14

public class Tea { final void prepareRecipe() { boilWater(); steepTeaBag(); // vyluhování čajového sáčku pourInCup(); addLemon(); // přidání citronu } ...

Z kódu vidíme, že dvě metody jsou stejné jako ve třídě Coffee, ale dvě jsou odlišné.

Obr. 2.2 Příprava kávy a čaje – diagram tříd UML Na obr. 2.2 je znázorněn další postup při řešení. Abstraktní třída CoffeineBeverage (kofejnový nápoj) má dvě podtřídy, z nichž každá předeklarovává metodu prepareRecipe(). Toto řešení dále upravíme tak, že zavedeme nové metody brew() a addCondiments() do metody prepareRecipe(). To pak znamená, že tato metoda nemusí být předeklarovaná v podtřídách. public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); // metoda předeklarovaná v podtřídách pourInCup(); addCondiments(); // metoda předeklarovaná v // podtřídách } abstract void brew(); // příprava nápoje abstract void addCondiments(); // přídání dochucovadel void boilWater() { System.out.println("Varici voda"); }

Page 15: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

15

void pourInCup() { System.out.println("Nalevani do salku"); } }

Implementace dvou uvedených metod v podtřídě Coffee. Podobně je tomu i ve třídě Tea. public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("Prekapani kavy přes filtr"); } public void addCondiments() { System.out.println("Pridani cukru a mleka"); } } Jak vidíme z předchozího postupu, návrhový vzor šablona definuje kroky algoritmu a dovoluje podtřídám implementovat některé z jeho kroků. Detailnější definice návrhového vzoru šablona je následující: Vzor šablona definuje kostru algoritmu v metodě, která odsouvá některé ze svých kroků do podtříd. Metoda šablona dovoluje podtřídám předefinovat jisté kroky algoritmu, beze změn struktury algoritmu. Dále je uvedena struktura této metody v programovém kódu: public abstract class AbstractClass { final void templateMethod() {

// metoda předeklarovaná v podtřídě primitiveOperation1(); // metoda předeklarovaná v podtřídě primitiveOperation2(); concreteOperation(); } abstract void primitiveOperation1(); abstract void primitiveOperation2(); void concreteOperation() { // implementace metody } void hook() { } }

Metoda templateMethod() je finální, což znamená, že nemůže být předeklarovaná v podtřídách. Ta vlastně tvoří kostru algoritmu. Metody primitivniOperace1,2() jsou abstraktní a musí být předeklarované v podtřídách. Metoda „hook()“ (háček, skoba) je

Page 16: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

16

konkrétní metoda a nemusí dělat nic. Pomáhá při nastavování defaultních hodnot. Viz následující kód: public abstract class CaffeineBeverageWithHook { void prepareRecipe() { boilWater(); brew(); pourInCup(); // zákazník chce přísady (cukr ..) if (customerWantsCondiments()) { addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } // metoda „hook()“, podtřída ho může ale // nemusí předeklarovat boolean customerWantsCondiments() { return true; } }

Následující třída je podtřídou třídy CaffeineBeberageWithHook. public class CoffeeWithHook extends CaffeineBeverageWithHook { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } // podtřída předeklaruje metodu nadtřídy public boolean customerWantsCondiments() { String answer = getUserInput(); if (answer.toLowerCase().startsWith("y")) { return true; } else { return false; } }

Page 17: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

17

private String getUserInput() { String answer = null; System.out.print("Would you like milk and sugar with your coffee (y/n)? "); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { answer = in.readLine(); } catch (IOException ioe) { System.err.println("IO error trying to read your answer"); } if (answer == null) { return "no"; } return answer; } } Jak je vidět z podtřídy, ta má možnosti metodu hook() předeklarovat a ptá se zákazníka, zda chce nebo nechce přísady do zvoleného nápoje. Následující programový kód ukazuje jednoduché použití popisovaného příkladu. public class BeverageTestDrive { public static void main(String[] args) { Coffee coffee = new Coffee(); System.out.println("\nMaking coffee..."); coffee.prepareRecipe(); CoffeeWithHook coffeeHook = new CoffeeWithHook(); System.out.println("\nMaking coffee..."); coffeeHook.prepareRecipe(); } }

Třída CaffeineBeverage je naše vysoko úrovňová komponenta. Ta má kontrolu nad algoritmem, který je uveden v metodě prepareRecipe() a volá podtřídy, pouze když jsou potřebné pro implementaci metody. Dalším příkladem je klasický příklad třídění. Třídící algoritmy se liší v přístupu a v rychlosti, ale každý třídící algoritmus spočívá na základních krocích porovnání dvou položek nebo atributů. Třídění je dávný příklad vzoru šablona. Je to postup, který nám dovoluje měnit jeden kritický krok a tím je porovnání dvou objektů, tak, aby se algoritmus dal použít vícekrát pro různé kolekce objektů.

Page 18: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

18

Třídy Array a Collection v Javě poskytují metodu sort(), která je deklarovaná jako třídní (statická) a pole (array), které se má setřídit, se jí zadává jako argument. Dále metoda někdy požaduje volitelný Comparator (rozhraní). Collection c = Collection.sort(array)

Naproti tomu třída ArrayList poskytuje instanční metodu sort(), která třídí příjemce této zprávy. ArrayList al = arrayList.sort() Oba přístupy jsou ale závislé na rozhraních Comparable a Comparator. Rozdíl mezi nimi je pouze v tom, že rozhraní Comparable očekává oba porovnávané objekty jako argumenty, zatímco Comparator očekává pouze jeden argument (porovnávaný objekt), druhý objekt představuje Comparator sám. Metoda sort() ve třídách Array a Collection dovoluje využívat ke třídění instance rozhraní Comparator. Pokud ji neuvedeme, metoda sort() bude využívat metodu compareTo() rozhraní Comparable. Většina primitivních typů včetně třídy String implementuje rozhraní Comparable. Třídění (řazení) primitivních typů probíhá podle implicitně implementovaného rozhraní Comparable v těchto typech (třídách). Objektový typ - obsahující více typů (objektových, primitivních) - nejdříve nutno implementovat rozhraní Comparable, aby bylo jasné, podle kterých datových atributů objektového typu se bude třídit (porovnávat). Rozhraní Comparable obsahuje následující metodu: public interface Comparable { public int compareTo(T o); }

Metoda compareTo() porovnává přijímající objekt (příjemce zprávy) se specifikovaným objektem jako argumentem metody a vrací: 0 obě hodnoty jsou stejné 1 příjemce je větší, než objekt specifikovaný jako argument (kladné

číslo) -1 příjemce je menší, než objekt specifikovaný jako argument

(záporné číslo) public class OsobaJmeno implements Comparable<OsobaJmeno> { private String jmeno, prijmeni; public OsobaJmeno(String jmeno, String prijmeni){ this.jmeno = jmeno; this.prijmeni = prijmeni;

Page 19: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

19

} public String getJmeno(){ return jmeno; } public String getPrijmeni(){ return prijmeni; } public boolean equals(Object o){ if(!(o instanceof OsobaJmeno)) return false; OsobaJmeno oj = (OsobaJmeno) o; return oj.getJmeno().equals(jmeno) && oj.getPrijmeni().equals(prijmeni); } public String toString(){ return "Jmeno: "+jmeno+" prijmeni: "+prijmeni; } public int compareTo(OsobaJmeno oj){ int porovnani = prijmeni.compareTo(oj.prijmeni); return (porovnani != 0 ? porovnani : jmeno.compareTo(oj.jmeno)); } }

public class OsobaJmenoTest { public static void main(String[] args) { OsobaJmeno o1, o2, o3, o4; o1 = new OsobaJmeno("Jan", "Zelenka"); o2 = new OsobaJmeno("Jan","Zehnal"); o3 = new OsobaJmeno("Jan","Zehnal"); o4 = new OsobaJmeno("Adam","Sedlacek"); System.out.println("o1 + o2 "+o1.compareTo(o2)); System.out.println("o2 + o3 "+o2.compareTo(o3)); System.out.println("o1 + o4 "+o1.compareTo(o4)); System.out.println("o4 + o3 "+o4.compareTo(o3)); } }

Využití třídění import java.util.*; public class OsobaJmenoSort { public static void main(String args[]){ OsobaJmeno poleJmen[] = { new OsobaJmeno("Jiri","Maly"), new OsobaJmeno("Odlrich","Maly"), new OsobaJmeno("Adam","Maly"), new OsobaJmeno("Alena","Mala"), new OsobaJmeno("Anna","Maliskova") }; List<OsobaJmeno> jmena = Arrays.asList(poleJmen); Collections.sort(jmena); System.out.println(jmena); } }

Page 20: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

20

Další porovnávání objektů je možné dělat s využitím rozhraní Comparator, které deklaruje dvě metody: public interface Comparator<t> { int compare(T o1, T o2); boolean equals(Object o); }

Třída TimeComparator implementuje rozhraní Comparator k porovnání dvou objektů třídy Time import java.util.Comparator; public class TimeComparator implements Comparator< Time2 > { public int compare( Time2 time1, Time2 time2 ) { // porovná hodiny int hourCompare = time1.getHour() - time2.getHour(); // nejdříve test hodin - hour if ( hourCompare != 0 ) return hourCompare; // porovná minuty int minuteCompare = time1.getMinute() - time2.getMinute(); // potom test minut if ( minuteCompare != 0 ) return minuteCompare; // porovná vteřiny int secondCompare = time1.getSecond() - time2.getSecond(); return secondCompare; // vrací výsledek porovnání } }

// Třídění seznamu s použitím tříd Comparator a // TimeComparator. import java.util.List; import java.util.ArrayList; import java.util.Collections; public class Sort3 { public void printElements() { // vytvoření seznamu List< Time2 > list = new ArrayList< Time2 >(); list.add( new Time2( 6, 24, 34 ) ); list.add( new Time2( 18, 14, 58 ) ); list.add( new Time2( 6, 05, 34 ) ); list.add( new Time2( 12, 14, 58 ) );

Page 21: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

21

list.add( new Time2( 6, 24, 22 ) ); // výstup prvků seznamu System.out.printf( "Unsorted array elements:\n%s\n", list ); // třídění s využitím comparator Collections.sort( list, new TimeComparator() ); // výstup prvků seznamu System.out.printf( "Sorted list elements:\n%s\n", list ); } // konec metody printElements() public static void main( String args[] ) { Sort3 sort3 = new Sort3(); sort3.printElements(); } }

Záměrem vzoru šablona je definovat algoritmus v metodě a nechat při tom některé kroky abstraktní, nebo definovat rozhraní, které může být implementováno rozdílně v různých třídách. Další třídy pak mohou doplňovat chybějící kroky, nebo implementovat rozhraní různě podle svých potřeb.

Page 22: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

3 Vzor Model-View-Controller (Observer)

V této kapitole se dozvíte:

• jak je možné sledování zmobjekty které ho sledují, není p

• jak je realizován mechanismus ší

• v jakých jiných oblastech se tento vzor vyskytuje.

Po jejím prostudování budete schopni:

• navrhnout a inmplementovat aplikace využívající tento vzor

• využívat třídy a rozhraní, které poskytuje platforma Javy.

Klí čová slova kapitoly:

model-view-controller, observer

Tento vzor dělí interaktivní aplikaci na tobsahuje jádro funkcionality a data, informace pro uživatele a ControllersViews a Controllers spolu zahrnují uživatelské rozhraní. Mechanismus šíření změn zabezpečuje konzistentnost mezi uživatelským rozhraním a modelem.

Kontext

Interaktivní aplikace s pružným rozhraním

Problém

Uživatelské rozhraní je náchylné vrozšíříte funkcionalitu aplikace, musíte modifikovat pnové funkce. Je-li uživatelské rozhraní těaplikace, je budování systému sk chybám. Tento postup ale vyústí vpodstatně odlišných programových systémuživatelské rozhraní. Následující požadavky ovlivň

22

Controller (Observer)

je možné sledování změn objektu tak, že mezi objektem a objekty které ho sledují, není přímá vazba,

jak je realizován mechanismus šíření změn,

jakých jiných oblastech se tento vzor vyskytuje.

jím prostudování budete schopni:

navrhnout a inmplementovat aplikace využívající tento vzor,

ídy a rozhraní, které poskytuje platforma Javy.

controller, observer

lí interaktivní aplikaci na tři komponenty. Model obsahuje jádro funkcionality a data, Views (pohledy) zobrazují

Controllers zpracovávají uživatelský vstup. spolu zahrnují uživatelské rozhraní. Mechanismus uje konzistentnost mezi uživatelským rozhraním a

pružným rozhraním člověk-počítač.

Uživatelské rozhraní je náchylné v požadavcích na změny. Když íte funkcionalitu aplikace, musíte modifikovat přístup k menu o

li uživatelské rozhraní těsně propojeno s funkcionálním jádrem je budování systému s uvedenou flexibilitou drahé a náchylné

ale vyústí v potřebu vývoje a údržby několika odlišných programových systémů, jeden pro každé

y ovlivňují řešení:

Page 23: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

23

• Stejné informace jsou prezentovány odlišně v různých oknech, např. sloupcový graf, koláčový graf, číselná tabulka.

• Zobrazování a chování aplikace musí odrážet okamžitou manipulaci s daty.

• Změny v uživatelském rozhraní by měly být snadné a možné i za běhu programu.

• Podpora různých standardů a portování uživatelského rozhraní by neměla ovlivňovat kód jádra aplikace.

Řešení

Model-View-Controller (MVC) byl poprvé uveden v objektově orientovaném programovém prostředí Smalltalk-80. MVC dělí interaktivní aplikaci na tři oblasti: model (výpočet), vstupy, výstupy. Komponenta model zapouzdřuje základní data a funkcionalitu. Model je nezávislý na specifikacích vstupního a výstupního chování aplikace. Komponenta View (pohled - výstup) zobrazuje uživateli informace. View dostává data od modelu. Na model může existovat několik pohledů. Každá komponenta View je asociovaná s komponentou controller (řízení - vstup). Controllers dostávají vstup obyčejně jako události, které jsou způsobeny pohybem myši, stisknutím tlačítka myši, nebo stisknutím klávesy na klávesnici. Tyto události jsou transformovány na požadavky služeb pro model nebo view. Uživatel má interakci se systémem výhradně prostřednictvím komponent controller. Separace komponenty model od komponent view a controller umožňuje násobné pohledy na stejný model. Pokud uživatel změní model prostřednictvím komponenty controller jedné komponenty view, všechny ostatní komponenty view závislé na těchto datech se také změní. Komponenta model proto uvědomuje všechny komponenty view, zda-li se jejich data změnila. Komponenty view na druhou stranu obnovují data z komponenty model a aktualizují zobrazované informace. Mechanismus propagace změn je také popsán ve vzoru Publisher-Subscriber.

Struktura

Komponenta model obsahuje funkcionální jádro aplikace. Zapouzdřuje data a exportuje procedury, které provádějí specifické zpracování aplikace. Komponenty controller volají tyto procedury místo uživatele. Komponenta model také poskytuje funkce pro přístup k jeho datům, které používají komponenty view, aby získaly a zobrazily data. Mechanismus šíření změn udržuje seznam závislých komponent uvnitř modelu. Všechny komponenty view a také vybrané komponenty

Page 24: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

24

controller registrují své potřeby být informován o změnách. Změny stavu modelu spouštějí mechanismus šíření změn. Tento mechanismus je reprezentován spojením komponent model s komponentami views a controllers. Komponenty view představují informace pro uživatele. Různé komponenty views prezentují informace modelu různým způsobem. Každá komponenta view definuje proceduru update(), která je aktivována mechanismem šíření změn. Když je zavolána procedura update(), komponenta view obnoví hodnoty aktuálních dat z modelu a zobrazí je na obrazovce. Během inicializace jsou všechny komponenty view asociovány s modelem a registrovány v mechanismu šíření změn. Každá komponenta view vytváří odpovídající komponentu controller. Mezi komponentami view a controller existuje asociace jedna k jedné. Komponenta controller přijímá vstupy od uživatele jako události. Platforma uživatelského rozhraní určuje, jak jsou tyto události doručeny. Pro jednoduchost předpokládejme, že každá komponenta controller implementuje proceduru zpracování události, která je vyvolána při každé relevantní události. Události jsou transformovány do požadavků pro komponenty model, nebo asociované komponenty view. Pokud chování komponenty controller závisí na stavu modelu, komponenta controller se registruje sáma u mechanismu šíření změn a implementuje proceduru update().

+attachObserver()+detachObserver()+notify()+getData()+service()

Model

-coreData-setOfObservers

+initialize(Model)()+makeController()+activate()+display()+update()

View

-myModel-myController

+initialize(Model, View)()+handleEvent()+update()

Controller

-myModel-myView

-callUpdate

-attach -getData

-attach -callService

-create

1

-manipulate display

1

+update()

Observer

Obr. 3.1 Základní struktura vzoru MVC s použitím diagramu tříd UML.

Page 25: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

25

Scénáře použití: 1. Ukazuje, jak uživatelský vstup, který způsobí změny v modelu,

spouští mechanismus šíření změn.

• Komponenta controller akceptuje uživatelský vstup prostřednictvím své procedury zpracovávající události, interpretuje událost a aktivuje služební proceduru modelu.

• Komponenta model vykoná požadovanou službu. Výsledkem je změna interních dat.

• Komponenta model uvědomí všechny komponenty view (pohledy) a controllers registrované v mechanismu šíření změn o změnách voláním jejich update proceduru.

• Každá komponenta view pak samostatně požaduje změněná data na komponentě model a zobrazí je na obrazovce.

• Každá registrovaná komponenta controller obnoví data z modelu, aby umožnila nebo zakázala jisté uživatelské funkce.

• Původní komponenta controller znovu získá řízení a vrací se z procedury zpracovávající událost.

Obr. 3.2 1. scénář pomocí diagramu sekvencí UML.

Controller Model View

handleEventservice

notify

update

display

getData

update

getData

Page 26: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

26

2. Ukazuje inicializaci MVC komponenty.

• Nejdříve je vytvořena instance komponenty model, která pak inicializuje vnitřní datové struktury.

• Je vytvořena instance komponenty view, která si bere odkaz na komponentu model, jako parametr inicializace.

• Komponenta view se registruje v mechanismu šíření změn modelu.

• Komponenta view pokračuje vytvořením controlleru. Controlleru předává jak referenci na sebe, tak i referenci na model.

• Komponenta controller se také registruje v mechanismu šíření změn modelu.

• Po inicializaci začne aplikace zpracovávat události.

Obr. 3.3 2. scénář (inicializace vzoru MVC) pomocí sekvenčního diagramu UML

main program

Model

View

Controller

initialize

attach

makeController

initialize

attach

startEventProcessing

Page 27: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

27

Implementace

1. Oddělení interakce člověk-počítač od jádra funkcionality.

2. Implementace mechanismu šíření změn.

3. Návrh a implementace komponent views.

4. Návrh a implementace komponent controllers.

5. Návrh a implementace vztahu komponent view-controller.

Výhody

Vícenásobné pohledy na stejný model. Synchronizované pohledy. Snadno zaměnitelné komponenty views a controllers.

Slabá místa

Vzrůstající složitost. Rychle rostoucí počet operací update. Neefektivnost v přístupu k datům v pohledu. Nevyhnutelnost změn komponent views a controllers při portování. Problémy s použitím MVC s moderními nástroji uživatelského rozhraní. Tato komponenta dělí aplikaci na tři části Model (data a operace nad nimi), View (pohledy) a Controllers (uživatelské vstupy). Tím se dosáhne toho, že jednotlivé části nejsou spolu tak provázané a mohou být snáze zaměnitelné.

Page 28: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

28

Observer V javovských knihovnách nalezneme implementovaný vzor MVC pod názvem Observer. Některé vzory mají více názvů (aliasů) vzniklých vývojem v čase. Cílem tohoto návrhového vzoru je definovat závislost one-to-many (jeden k mnoha) mezi objekty. Jeden objekt – sledovaný (observable); mnoho objektů – sledovatelé (observers), závislé objekty. Průběh sledování: když se ten jeden (sledovaný) objekt změní, jsou o tom informováni všichni sledovatelé - „závislé“ objekty, takže mohou reagovat na změnu. Java v tomto směru poskytuje pro sledovaný objekt třídu java.util.Observable. Sledovaný objekt musí být podtřídou této třídy. Dále tato třída poskytuje metody setChange() a notifyObservers(), které je třeba vyvolat při každé změně. Každý objekt typu Observer (sledovatel) musí být v úvodu zaregistrovaný u objektu typu Observable pomocí metody addObserver(Observer o), která přidá objekt typu Observer do seznamu objektů, které sledují objekt typu Observable. Objekty (observers), které sledují objekt (Observable object) musí implementovat rozhraní Observer to znamená implementovat metodu update(Observable os, Object ob) se dvěma argumenty. První argument je sledovaný objekt, druhý pak libovolný objekt, který může eventuálně poskytovat dodatečné informace. Observer – praktická ukázka Celý návrhový vzor je demonstrován na jednoduché ukázce s jedním účtem a objekty (instance) otec a matka třídy Rodic, které na účet přispívají a objekty třídy Student, které z účtu v okamžiku, když tam přijdou peníze, peníze čerpají. V příkladu jsou uvedeni dva studenti, kteří čerpají stejným dílem. Třída Ucet je deklarovaná jako podtřídy Observable (pozorovaný objekt). Objektu třídy student implementují rozhraní Observer. import java.util.Observable; public class Ucet extends Observable { private int cislo; private int stav; public Ucet() { this(0, 0, null, null); } public Ucet(int cislo, int stav, Student s1, Student s2) { this.cislo = cislo; this.stav = stav; addObserver(s1); addObserver(s2); }

Page 29: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

29

public void vlozeni (int castka) { stav = stav + castka; System.out.printf("Ucet vlozeni: %d stav: %d\n",castka, getStav()); setChanged(); notifyObservers(castka); } public void vyber (int castka) { stav = stav - castka; System.out.printf("Ucet vyber: %d zustatek: %d\n",castka,getStav()); } public int getStav(){ return stav; } public String toString() { String tx = "Cislo uctu: " + cislo + " stav: " + stav; return tx; } public void tisk() { System.out.println(this.toString()); } public int getCislo(){ return cislo; } } public class Osoba { private String jmeno; private String bydliste; public Osoba(String jmeno, String bydliste) { this.jmeno= jmeno; this.bydliste = bydliste; } public void setJmeno(String jmeno) { this.jmeno = jmeno; } public String getJmeno(){ return jmeno; } public void setBydliste(String bydliste) { this.bydliste = bydliste; } public String getBydliste() { return bydliste; } public String toString() { return String.format("%5s %s %s %s\n", "Jmeno",getJmeno(), "bydliste:",getBydliste()); } public void tiskKlienta() { System.out.println(this.toString()); } }

Page 30: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

30

public class Rodic extends Osoba { private Ucet ucet; public Rodic(String jmeno, String bydliste, Ucet u){ super(jmeno, bydliste); ucet = u; } public void vlozit(int castka){ ucet.vlozeni(castka); System.out.println(getClass().getName()+ " vlozil castku: "+castka); } } import java.util.Observer; import java.util.Observable; public class Student extends Osoba implements Observer{ public Student(String jmeno, String bydliste){ super(jmeno, bydliste); } public void update(Observable ob, Object castka){ int celkem = ((Ucet)ob).getStav(); int penize; if(celkem<=0)penize = 0; else penize = (Integer)castka /2; ((Ucet)ob).vyber(penize); System.out.println(getClass().getName()+ " vybral castku: " + penize); } }

public class TestObserver { public static void main(String[] args) { Student karel = new Student("Karel","Praha"); Student jarmila = new Student("Jarmila","Olomouc"); Ucet ucet = new Ucet(1, 200, karel, jarmila); Rodic otec = new Rodic("Josef", "Havirov", ucet); Rodic matka = new Rodic("Alena", "Havirov", ucet); otec.vlozit(500); matka.vlozit(1200); } }

U objektů karel a jarmila třídy Student není třeba explicitně provádět operaci vyběr, protože ta je provedena v metodě update(). Dále si ještě uvedeme jednoduchý příklad využívající grafického uživatelského rozhraní. Jedná se o čítač, který má tři tlačítka pro zvýšení čítače, snížení čítače a jeho nulování. Vše zabezpečuje komponenta listener, která „poslouchá“, zda nenastala nějaká událost. Pokud událost nastane, je komponenta listener (posluchač) automaticky „upozorněna“ a provede zadané operace. Nejdříve uvedeme třídu Model, tedy vlastní čítač.

Page 31: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

31

public class Citac { private int pocet; public Citac(int cislo) { pocet = cislo; } public void incr() {pocet++; } public void decr() {pocet--; } public void clr() {pocet = 0; } public int getCnt() {return pocet; } }

Dále uvedeme komponenty View a Controller, které jsou spolu ve třídě CitacFrame. import java.awt.FlowLayout; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.JLabel; import javax.swing.JButton; public class CitacFrame extends JFrame{ private Citac citac; private JButton increment, decrement, clear; private JTextField field; private JLabel label; public CitacFrame() { super("Jenoduchý čítač"); setLayout(new FlowLayout()); label = new JLabel("Stav čítače: "); add(label); field = new JTextField(6); field.setEditable(false); field.setHorizontalAlignment(JTextField.RIGHT); add(field); field.setText(String.valueOf(this.getCitac().getCnt())); increment = new JButton("Increment"); add(increment); decrement = new JButton("Decrement"); add(decrement); clear = new JButton("Clear"); add(clear); ButtonHandler handler = new ButtonHandler(); increment.addActionListener(handler); decrement.addActionListener(handler); clear.addActionListener(handler); } // vnitřní třída private class ButtonHandler implements ActionListener { public void actionPerformed(ActionEvent event) { if(event.getSource()==increment) getCitac().incr(); else if(event.getSource()==decrement) getCitac().decr();

Page 32: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

32

else if(event.getSource()==clear) getCitac().clr(); // úprava zobrazeni položky field.setText(String.valueOf(getCitac().getCnt())); } } public Citac getCitac() { if(citac == null) citac = new Citac(0); return citac; } }

Hlavní metoda pro celou aplikaci jejiž výsledek vidíte: public class CitacFrameTest { public static void main(String args[]) { CitacFrame counterFrame= new CitacFrame(); counterFrame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); counterFrame.setSize(400, 200); counterFrame.setVisible(true); } }

Často je model MVC (Model-View-Controller) spojován s webovými aplikacemi. Ukážeme si proto jednoduchý principielní obrázek jak vypadá struktura takové aplikace.

Vzor MVC a Web Také webový vývojáři využívají vzor MVC. Převažující adaptace tohoto vzoru používá kombinací servletu a technologie JSP (JavaServer Pages) k uchování oddělení modelu, pohledu a kontroleru.

Page 33: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

33

Obr. 3.4 Schematické zobrazení vzoru MVC využívaném při webových aplikacích Jednotlivé operace jsou na obrázku označeny číslicemi od 1 do 5. 1. Vytvoříte „HTTP request“, který p řijme servlet.

Tato operace typicky zahrnuje, že spolu s dotazem budou zaslána data v nějaké formě jako např. uživatelské jméno a heslo. Servlet dostane tato data a syntakticky je analyzuje (parsuje).

2. Servlet pracuje jako kontroler a zpracuje váš dotaz většinou jako další (následný) dotaz na model (v tomto případě databázi). Výsledek zpracování dotazu je obyčejně vytvořen (zformován) do formy JavaBeanu. JavaBean představuje model, protože obsahuje také tzv. business logiku tedy metody pro práci s modelem. Databáze většinou již představuje jen perzistenci vložených objektů, proto nepředstavuje model.

3. Kontroler p řesouvá řízení na view (pohled). View je reprezentované JSP (JavaServer Pages). Jedinou činností JSP je vytvořit stránku, která představuje pohled modelu (view of model).

4. Tento pohled JSP získá z JavaBeanu, spolu s dalšími příkazy pro řízení nutnými pro další akce.

5. Pohled vrací stránku prohlížeči prostřednictvím protokolu HTTP . Stránka je vrácena prohlížeči, kde je zobrazena jako pohled (view). Uživatel pak zadá další požadavek, který je zpracován podobným způsobem.

Výhodou tohoto vzoru je nejen to, že odděluje komponenty (jednotlivé součásti) v návrhu, ale také poskytuje oddělení při tzv. výrobních (produkčních) odpovědnostech.

Page 34: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

34

Page 35: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

4 Vzory Dekorátor a Visitor

V této kapitole se dozvíte:

• jak

• jak

tříd v

Po jejím prostudování budete schopni:

• rozum

aplikovat

Klí čová slova této k

• dekorátor, visitor, skládání objekt

4.1 Vzor Dekorátor (Decorator)

Kontext: Malé změ Problém:Jak využít skládání pro dynamické zm Řešení: Návrhový vzor Deoperace za b Představme si, že máme tpotřebujeme doplnit o další funkcionalitu. Pod touto pfunkcionalitou máme na mysli vykonání nnebo po dané operaci. Jedno danou třídou vytvopseudoproma řízení přfunkcionalitu ppředka, tak potomka. Přístup využívající dv tom, že dPokud ji použPokud potdědění, což nelze u

Vzory Dekorátor a Visitor

této kapitole se dozvíte:

ak využít skládání pro dynamické chování objektu

jak definovat novou operaci v hierarchické struktu

tříd v této hierarchii.

Po jejím prostudování budete schopni:

rozumět návrhovým vzorům dekorátor a visitor a dokázat je

aplikovat.

ová slova této kapitoly:

dekorátor, visitor, skládání objektů, hierarchie tř

4.1 Vzor Dekorátor (Decorator)

Malé změny chování objektu za běhu programu.

: Jak využít skládání pro dynamické změny chování objektu.

Návrhový vzor Dekorátor, který dovoluje skládat nové varianty operace za běhu programu.

edstavme si, že máme třídu s danou operací. Tuto operaci (metodu) ebujeme doplnit o další funkcionalitu. Pod touto p

funkcionalitou máme na mysli vykonání něčeho navíc a to bunebo po dané operaci. Jedno řešení je použít dědičdanou třídou vytvoříme její podtřídu a v operaci podtpseudoproměnné super odkážeme na metodu nadtřídy, která se provede

ízení předá zpět do metody podtřídy. Tímto způsobem jsme rozšífunkcionalitu při zachování kompatibility, protože mů

edka, tak potomka.

ístup využívající dědění má však svoji nevýhodu, která spotom, že dědění je statickou vazbou, kterou již nadále nezm

Pokud ji použijeme, musíme počítat s omezenou možností kombinací. Pokud potřebujeme novou kombinaci, tak musíme provést další nové

ní, což nelze učinit v době běhu programu.

35

skládání pro dynamické chování objektu,

hierarchické struktuře bez změny

m dekorátor a visitor a dokázat je

, hierarchie tříd

ny chování objektu.

který dovoluje skládat nové varianty

danou operací. Tuto operaci (metodu) ebujeme doplnit o další funkcionalitu. Pod touto přídavnou

eho navíc a to buď před, ědičnosti, tedy pod

operaci podtřídy se pomocí řídy, která se provede

obem jsme rozšířili i zachování kompatibility, protože můžeme použít jak

ní má však svoji nevýhodu, která spočívá ní je statickou vazbou, kterou již nadále nezměníme.

omezenou možností kombinací. ebujeme novou kombinaci, tak musíme provést další nové

Page 36: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

36

Vzor Dekorátor ukazuje, jak dosáhnout rozšíření (extenzi) funkcionality nikoli děděním, ale flexibilním skládáním. Při představě více prvků za sebou tato struktura připomíná zřetězený seznam. Pro plnou flexibilitu musí být shodné rozhraní propojení mezi prvky. Scénář volání operací používá jedno stejné rozhraní. V příkladě, který následuje, jsou to metody getTotalCost() a getDescription(). Další postup si vysvětlíme na příkladě kavárny, která nabízíme jednak základní druhy káv (Espresso, Cappuccino, CafeMocha) a dále doplňky, které se mohou libovolně kombinovat (ExtraEspresso, Whipped, Decaf). U výsledného produktu vyžadujeme jednak název (obsahuje základní kávu a všechny „doplňky“ a také celkovou cenu. Struktura tříd je uvedena na následujícím obrázku. Z diagramu tříd je vidět, že všechny třídy mají implementované již zmíněné dvě metody. Abstraktní třída Dekorátor představuje napojení dalších tříd, které představují „doplňky“ ke kávě. Navíc také třídy obsahují konstruktor, který volá konstruktor Dekorátoru, pro uložení typu základní kávy.

Obr. 4.1 Diagram tříd UML návrhového vzoru Decorator Následuje programový kód tříd uvedených v diagramu tříd na obr. 4.1.

Page 37: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

37

public interface DrinkComponent { float getTotalCost(); String getDescription(); } public class Espresso implements DrinkComponent { private String description = "Espresso"; private float cost = 0.75f; public float getTotalCost() { return cost; } public String getDescription() { return description; } } public class Cappuccino implements DrinkComponent { private float cost = 1; private String description = "Cappuccino"; public float getTotalCost() { return cost; } public String getDescription() { return description; } } public class CafeMocha implements DrinkComponent { private float cost = 1.25f; private String description = "Cafe Mocha"; public float getTotalCost() { return cost; } public String getDescription() { return description; } } public abstract class Decorator implements DrinkComponent { private DrinkComponent component; public Decorator(DrinkComponent component) { this.component = component; } public DrinkComponent getComponent() { return component; } public float getTotalCost() { return getComponent().getTotalCost(); } public String getDescription() { return getComponent().getDescription(); } }

Page 38: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

38

public class ExtraEspresso extends Decorator { private float cost = 0.75f; public ExtraEspresso(DrinkComponent component) { super(component); } public String getDescription() { return getComponent().getDescription() + " extra espresso"; } public float getTotalCost() { return cost + getComponent().getTotalCost(); } } public class Whipped extends Decorator { private float cost = 0.50f; public Whipped(DrinkComponent component) { super(component); } public float getTotalCost() { return cost + getComponent().getTotalCost(); } public String getDescription() { return getComponent().getDescription() + " whipped cream"; } } public class Decaf extends Decorator { public Decaf(DrinkComponent component) { super(component); } public String getDescription() { return getComponent().getDescription() + " decaf"; } } public class CoffeeShop { public static void main(String[] args) { DrinkComponent cappuccino = new Cappuccino(); System.out.println(cappuccino.getDescription() + ": $" + cappuccino.getTotalCost()); DrinkComponent cafeMocha = new Whipped( new Decaf(new CafeMocha())); System.out.println(cafeMocha.getDescription() + ": $" + cafeMocha.getTotalCost()); DrinkComponent expresso = new Whipped( new Espresso()); System.out.println(expresso.getDescription() + ": $" + expresso.getTotalCost()); } }

Page 39: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

39

Z uvedeného příkladu je vidět, že flexibilita řešení je daná vkládáním nových instancí do sebe. Vzor Dekorátor můžeme použít všude tam, kde by řešení rozšíření funkcionality děděním přinášelo problémy se vznikem kombinací výsledných řešení, to je problémy se statickou neměnnou vazbou. Příklady využití uvedeného vzoru můžeme nalézt v javovské knihovně pro práci se streamy. Tam můžeme využívat vkládání instancí „do sebe“. Vzor Dekorátor poskytuje flexibilní návrh v případech, kdy potřebujeme kombinovat různé implementace operací za běhu programu.

Page 40: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

40

4.2 Visitor Kontext: Je daná základní hierarchie tříd daného problému, do které není možné dělat změny. Může to být hierarchie tříd dodaná jiným dodavatelem. Jako změny myslíme dodání dalších polymorfických metod, které by rozšířily funkčnost stávající hierarchie tříd. Problém: Je třeba dodat další metody do hierarchie tříd bez fyzického přidání daných metod do každé třídy uvedené hierarchie. Řešení: Řešení přináší návrhový vzor visitor (návštěvník), který dovoluje vytvoření oddělené hierarchie tříd typu Visitor k virtualuizaci operací prováděných na původní hierarchii tříd.

Obr. 4.2 Diagram tříd UML zachycující jednoduché použití vzoru Visitor. Neměnnou strukturu tříd představují třídy na levé straně obrázku, tedy třídy Tvar, Kruh a Ctverec. Na pravé straně obrázku je pak hierarchie tříd, které se využívají na rozšíření funkčnosti stávající hierarchie. Jedná se nám o to, že chceme doplnit metody pro výpočet obsahu a obvodu daných obrazců. Postup při použití vzoru je následující: Ve stávající (neměnné) hierarchii tříd doplníme metodu accept() s argumentem instancí rozhraní Visitor do každé třídy. Abstraktní třída Tvar deklaruje metodu accept() jako abstraktní a její podtřídy ji pak implementují podle svých skutečných potřeb. Formální tvar je stejný, jen pseudoproměnná this je různá.

Page 41: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

41

public double accept(Visitor v) { return v.visit(this); }

Podle počtu rozšiřujících metod, o které chceme rozšířit stávající hierarchii tříd, doplníme třídy implementující rozhraní Visitor. V našem případě se jedná o třídy VisitObsah a VisitObvod, protože chceme doplnit operace počítající obsah a obvod obrazců. Dále musíme vytvořit ve třídách VisitObsah a VisitObvod tolik metod visit(), kolik máme různých obrazců. Protože máme dva obrazce (Square a Circle), stačí nám dvě metody visit(). public class VisitObvod implements Visitor{ public double visit(Kruh c) { return 2 * Math.PI * c.getPolomer(); } public double visit(Ctverec s){ return 4 * s.getStrana(); } }

Jedna metoda visit() počítá obvod kruhu a druhá metoda visit počítá obsah kruhu. Pro větší názornost uvedeme celý kód jednoduchého příkladu: public abstract class Tvar { public abstract void vykresli(); public abstract void smaz(); public abstract double accept(Visitor v); } public class Kruh extends Tvar{ private int polomer; public Kruh() { polomer = 16; } public double accept(Visitor v) { return v.visit(this); } public int getPolomer() { return polomer; } public void vykresli() { System.out.println("Kruh.vykresli"); } public void smaz() { System.out.println("Kruh.smaz"); } }

Page 42: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

42

public class Ctverec extends Tvar{ private int strana; public Ctverec() { strana = 12; } public double accept(Visitor v) { return v.visit(this); } public int getStrana() { return strana; } public void vykresli() { System.out.println("Ctverec.vykresli"); } public void smaz() { System.out.println("Ctverec.smaz"); } } public interface Visitor { public double visit(Kruh c); public double visit(Ctverec s); } public class VisitObsah implements Visitor{ public double visit(Kruh c){ return Math.PI * c.getPolomer() * c.getPolomer(); } public double visit(Ctverec s){ return s.getStrana() * s.getStrana(); } } public class VisitObvod implements Visitor{ public double visit(Kruh c) { return 2 * Math.PI * c.getPolomer(); } public double visit(Ctverec s){ return 4 * s.getStrana(); } } public class TvarTest { public static void main(String args[]) { VisitObsah visitObsah = new VisitObsah(); VisitObvod visitObvod = new VisitObvod(); List <Tvar>tvary = new ArrayList<Tvar>(); tvary.add(new Ctverec()); tvary.add(new Kruh()); tvary.add(new Kruh()); tvary.add(new Ctverec()); tvary.add(new Kruh()); Iterator<Tvar>itr = tvary.iterator(); while(itr.hasNext()) { Tvar s = itr.next(); s.vykresli();

Page 43: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

43

s.smaz(); System.out.printf("Obsah obrazce: %s je: %.2f\n", s.getClass().getName(), s.accept(visitObsah)); System.out.printf("Obvod obrazce: %s je: %.2f\n", s.getClass().getName(), s.accept(visitObvod)); } } }

Po spuštění programu dostaneme následující výsledky: Ctverec.vykresli Ctverec.smaz Obsah obrazce: Ctverec je: 144,00 Obvod obrazce: Ctverec je: 48,00 Kruh.vykresli Kruh.smaz Obsah obrazce: Kruh je: 804,25 Obvod obrazce: Kruh je: 100,53 Kruh.vykresli Kruh.smaz Obsah obrazce: Kruh je: 804,25 Obvod obrazce: Kruh je: 100,53 Ctverec.vykresli Ctverec.smaz Obsah obrazce: Ctverec je: 144,00 Obvod obrazce: Ctverec je: 48,00 Kruh.vykresli Kruh.smaz Obsah obrazce: Kruh je: 804,25 Obvod obrazce: Kruh je: 100,53

Je zřejmé, že můžeme zavést novou operaci (metodu) nad libovolnou strukturou, a to pouze dodáním nové třídy implementující rozhraní Visitor. Problém ale nastane, pokud rozšíříme stávající neměnnou strukturu tříd o další třídu. To však odporuje úvodnímu předpokladu, že struktura tříd je neměnná.

Page 44: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

5 Vzor Továrna (Factory)

V této kapitole se dozvíte:

• jak vydělit vytváření instancí do jiné t

• jak definovat rozhraní pro t

• jak umožnit vytvářet skupiny „p

Po jejím prostudování budete schopni:

• lépe se orientovat v problematice vytvá

• budete rozumět programovému kódu využívající „továrny“.

Klí čová slova této kapitoly:

• jednoduchá továrna, tovární metoda, abstraktní továrna

5.1 Jednoduchá Továrna

Při vytváření třídy obvykle do ní doplníme konstruktory na tvorbu instancí. Metoda new(), pomocí které vytvášpatná, ale problémy se mohou objevit pfunkčnosti a přidáváním dalších podtže je vždy spojena s konkrétní totevřený pro rozšiřování a také blízký pro modimplementaci využíváme princip „programování proti rozhraní“. To jednoduše znamená, že promnejvyšší možnou třídu (většinou rozhraní) a za klína pravé straně výrazu se objeví konkrétní tvýhodný v tom, že přímo podporuje polymorfismus, tedy promlevé straně standardního výrazu pak instance tříd, které implementují dané rozhraní. Tento princip bude ukázán na konkrétním příkladě Pokud chceme vyřešit výše zmínvázána vždy na konkrétní třídu, musíme oddse mění, od těch, které se nemchceme vytvářet instance tříd rozhraní Tvar. Využití principu programování prot Tvar tvar = new Kruh(); tvar = new Ctverec();

Vlastní vytváření insatncí od tmění. Vytvoříme proto novou t

44

ření instancí do jiné třídy,

jak definovat rozhraní pro třídy vytvářející instance,

řet skupiny „příbuzných“ objektů.

jejím prostudování budete schopni:

problematice vytváření instancí,

t programovému kódu využívající „továrny“.

ová slova této kapitoly:

jednoduchá továrna, tovární metoda, abstraktní továrna

Jednoduchá Továrna

ídy obvykle do ní doplníme konstruktory na tvorbu pomocí které vytváříme nové instance není

špatná, ale problémy se mohou objevit při změnách, tedy rozšiřování idáváním dalších podtříd. Problémem metody new() je,

konkrétní třídou. Programový návrh by měl být ování a také blízký pro modifikaci. Při vlastní

implementaci využíváme princip „programování proti rozhraní“. To jednoduše znamená, že proměnná na levé straně je kvalifikovaná na

ětšinou rozhraní) a za klíčovým slovem new výrazu se objeví konkrétní třída. Tento postup je

ímo podporuje polymorfismus, tedy proměnná na standardního výrazu pak může ukazovat na všechny

íd, které implementují dané rozhraní. Tento princip bude říkladě později v této kapitole.

ešit výše zmíněné problémy metody new(), která je vázána vždy na konkrétní třídu, musíme oddělit aspekty aplikace, které

ch, které se nemění. V našem konkrétním příkladu et instance tříd Kruh a Ctverec, které obě implementují

. Využití principu programování proti rozhraní:

ení insatncí od tříd Kruh a Ctverec je ta část, která se íme proto novou třídu, kterou nazveme Tovarna (Factory)

Page 45: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

45

a do ní umístíme všechny části, které jsou spojené s vytvářením instancí, tedy co se mění. Této třídě musíme samozřejmě předat parametr, podle kterého se rozhodne, jaký objekt má vytvořit. Tento parametr se obvykle předává v textové formě, jako parametr typu String, ale může být využit také výčtový typ. Kód této třídy je pak následující: public class Tovarna { public Tovarna() {} // konstruktor public Tvar vytvorit(String typ) { if(typ.equals("Kruh")) return new Kruh(); if(typ.equals("Ctverec")) return new Ctverec(); throw new RuntimeException( "Spatny typ tvaru: " + typ); } }

Třídu Tovarna tvoří bezparametrický konstruktor a metoda vytvorit() s parametrem String, podle kterého metoda rozhoduje, jakou instanci má vytvořit. Pro úplnost si nejdříve uvedeme diagram tříd celé jednoduché továrny a pak ještě zbylý kód.

Obr. 5.1 Jednoduchá továrna (Simple Factory) Následuje kód rozhraní Tvar, tříd Kruh a Ctverec. public interface Tvar { public abstract void nakresli(); public abstract void smaz(); } public class Kruh implements Tvar { public Kruh() {} public void nakresli() { System.out.println("Kruh.nakresli"); } public void smaz() { System.out.println("Kruh.smaz"); } }

Page 46: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

46

public class Ctverec implements Tvar { public Ctverec() {} public void nakresli() { System.out.println("Ctverec.nakresli"); } public void smaz() { System.out.println("Ctverec.nakresli"); } }

Třída TvarVyroba má konstruktor, který zabezpečí napojení této třídy na třídu Tovarna a metodu novyTvar(), která vytváří objektu. public class TvarVyroba { private Tovarna tovarna; public TvarVyroba(Tovarna tovarna) { this.tovarna = tovarna; } public Tvar novyTvar(String typ){ return tovarna.vytvorit(typ); } }

Pro úplnost ještě uvedeme třídu TovarnaTest, která ukazuje jednoduché použití tohoto vzoru. public class TovarnaTest { public static void main(String args[]) { TvarVyroba tvarVyroba = new TvarVyroba(new Tovarna()); Tvar s = tvarVyroba.novyTvar("Kruh"); s.nakresli(); s.smaz(); s = tvarVyroba.novyTvar("Ctverec"); s.nakresli(); s.smaz(); String shlist[] = { "Kruh", "Ctverec", "Ctverec", "Kruh", "Kruh", "Ctverec" }; List<Tvar> Tvars = new ArrayList<Tvar>(); Iterator<String> it = Arrays.asList(shlist).iterator(); while(it.hasNext()) Tvars.add(tvarVyroba.novyTvar(it.next())); Iterator<Tvar>itr = Tvars.iterator(); while(itr.hasNext()) { s = itr.next(); s.nakresli(); s.smaz(); } System.out.println("Konec programu"); } }

V úvodu je uvedené jednoduché využití a následuje pak ukládání objektů do kolekce ArrayListu a následný tisk.

Page 47: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

47

Jak je vidět z kódu jednoduché aplikace, podstatou jednoduché továrny je vyčlenění vytváření instancí do třídy Továrna. Instance třídy Továrna se pak může předávat v konstruktoru do jiné třídy, která využívá instanci Továrny k vytváření požadovaných objektů.

Page 48: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

48

5.2 Tovární metoda (Factory Method) Kontext: Při vytváření třídy obyčejně doplníme konstruktory na tvorbu instancí. Někdy klient potřebuje nový objekt, ale neví, od které z několika možných tříd vytvořit instanci (hierarchie). Problém: Vytváření instancí (objektů), když nejsou k dispozici všechny potřebné informace. Řešení: Tovární metoda umožňuje tvůrci tříd definovat rozhraní pro vytvářený objekt, zatímco si ponechává volbu, ze které třídy instanci vytvoří. Rozpoznání Metody Factory Je mylná domněnka, že každá metoda, která vytváří a vrací objekt je tovární metoda. V OOP je běžné, že metody vrací nový objekt. Tovární metoda je metoda, která jednak vytváří nový objekt a dále izoluje klienta od povědomí, od které třídy vytváří objekt. Při požadavku na objekt, je definitivní třída objektu, který bude vytvořen závislá na chování objektu Tovarna, který dostal požadavek na vytvoření nového objektu. Tovární metoda dále rozšiřuje možnosti jednoduché továrny a to v tom smyslu, že nezavádí pouze jednu továrnu, ale továrnu ve formě rozhraní nebo abstraktní třídy a od ní odvozené tzv. konkrétní továrny. Celá problematika je nejlépe vidět na schematickém diagramu tříd.

Obr. 5.2 Diagram tříd tovární metody Z obrázku je vidět již zmíněná vlastnost, že zavádíme konkrétní továrny, které jsou podtřídami nebo implementují Tovarnu. Třída Produkt představuje v našem příkladě třídu Tvar a konkrétní produkty

Page 49: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

49

jsou pak třídy Kruh a Ctverec. Konkretní továrny budou instance tříd Kruh a Ctverec zobrazovat buď v dvou-dimenzionálním prostoru, nebo ve tří-dimenzionálním prostoru. Takže z toho důvodu zavedeme Tovarnu2D pro dvou-dimensionální prostor a Tovarnu3D pro tří-dimensionální prostor. Nejdříve uvedeme kódy metod tříd spojených s Produktem: public interface Tvar { public abstract void nakresli(); public abstract void smaz(); } public class Ctverec2D implements Tvar { public Ctverec2D() {} public void nakresli() { System.out.println("Ctverec2D.nakresli"); } public void smaz() { System.out.println("Ctverec2D.smaz"); } } public class Kruh3D implements Tvar { public Kruh3D() {} public void nakresli() { System.out.println("Kruh3D.nakresli"); } public void smaz() { System.out.println("Kruh3D.smaz"); } }

Samozřejmě že musejí existovat ještě třídy Ctverec3D a třída Kruh2D, které jsou analogické třídám, které jsme uvedli (proto je neuvádíme). public abstract class Tovarna { public abstract Tvar vytvorit(String typ); } public class Tovarna2D extends Tovarna { @Override public Tvar vytvorit(String typ) { if(typ.equals("Kruh")) return new Kruh2D(); if(typ.equals("Ctverec")) return new Ctverec2D(); throw new RuntimeException( "Spatny typ tvaru: " + typ); } } public class Tovarna3D extends Tovarna { @Override public Tvar vytvorit(String typ) {

Page 50: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

50

if(typ.equals("Kruh")) return new Kruh3D(); if(typ.equals("Ctverec")) return new Ctverec3D(); throw new RuntimeException( "Spatny typ tvaru: " + typ); } }

Konkrétní továrny implementují pouze jednu metodu a to vytvorit(), která je deklarovaná v rozhraní. public class TovarnaTest { public static void main(String args[]) { Tovarna tovarna2D = new Tovarna2D(); Tovarna tovarna3D = new Tovarna3D(); Tvar s = tovarna2D.vytvorit("Kruh"); s.nakresli(); s.smaz(); s = tovarna3D.vytvorit("Ctverec"); s.nakresli(); s.smaz(); String shlist[] = { "Kruh", "Ctverec", "Ctverec", "Kruh", "Kruh", "Ctverec" }; List<Tvar> Tvars = new ArrayList<Tvar>(); Iterator<String> it = Arrays.asList(shlist).iterator(); while(it.hasNext()) { Tvars.add(tovarna2D.vytvorit(it.next())); Tvars.add(tovarna3D.vytvorit(it.next())); } Iterator<Tvar>itr = Tvars.iterator(); while(itr.hasNext()) { s = itr.next(); s.nakresli(); s.smaz(); } } } Jak je z kódu vidět, nejdříve si vytvoříme instance od konkrétních továren, tedy Tovarny2D a Tovarny3D. Ty pak vyvolávají metodu vytvořit() s patřičným parametrem, podle kterého se rozhodne, zda vytvoříme kruh nebo ctverec. Instance továren rozhodují, zda to bude v prostoru 2D či 3D. Tovární metoda je výhodná pokud existuje jeden konkrétní tvůrce (creator), protože implementace produktu je oddělena od jeho použití. Rozšiřuje jednoduchou továrnu o možnost vytvářet ne jednu továrnu, ale celou řadu továren na tvorbu objektů.

Page 51: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

51

5.3 Abstraktní továrna (Abstract Factory) Kontext: Někdy chcete vytvořit objekt, zatím ale není jasné od které třídy. Problém: Vytvoření rodiny souvisejících nebo závislých objektů, přičemž klient je izolován od volání těchto tříd. Řešení: V tomto případě možno použít vzor Metoda Factory s metodou, která používá vnější faktor, který řídí, od které třídy se vytvoří instance. Vzor Abstraktní továrna je podobný objektům továrny z předchozího příkladu, ale s více továrními metodami. Každá z těchto továrních metod vytváří odlišný typ objektu. Myšlenka je, že v době tvorby objektu Factory rozhodnete, jak tyto objekty vytvořené Factory použijete.

Obr. 5.3 Diagram tříd abstraktní továrny Abstraktní továrna je dalším rozšířením tovární metody. Toto rozšíření spočívá v tom, že nepracujeme pouze s jedním produktem, ale celou řadou produktů. V našem jednoduchém příkladě budeme pracovat pouze se dvěma produkty, z nichž každý bude mít buď abstraktní třídu, nebo rozhraní. Abstraktními produkty budou Kruh a Ctverec a

Page 52: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

52

konkrétními produkty pak Kruh2D, Kruh3D, Ctverec2D a Ctverec3D. Pro všeobecnou představu o tomto návrhovém vzoru si nejdříve uvedeme diagram tříd (obr. 5.3), který přehledně zachycuje celou situaci. Z diagramu je názorně vidět, že konkrétní továrny mají vazby na konkrétní produkty, tedy Tovarna2D má vazbu na Kruh2D a Kruh3D, stejně jako Tovarna3D má vazbu na Kruh3D a Ctverec3D. Naopak třída Klient má vazbu na abstraktní třídy nebo rozhraní, reprezentované třídami Kruh, Ctverec a Tovarna. Představíme si nyní kódy jednotlivých tříd a začneme třídami produktu, tedy třídami Kruh a Ctverec. public interface Ctverec { public abstract void nakresli(); public abstract void smaz(); } public class Ctverec2D implements Ctverec { public Ctverec2D() {} public void nakresli() { System.out.println("Ctverec2D.nakresli"); } public void smaz() { System.out.println("Ctverec2D.smaz"); } } public class Ctverec3D implements Ctverec { public Ctverec3D() {} public void nakresli() { System.out.println("Ctverec3D.nakresli"); } public void smaz() { System.out.println("Ctverec3D.smaz"); } }

Třída Kruh je řešena podobně a proto ji neuvádíme. Následují třídy továren: public interface Tovarna { public Kruh vytvorKruh(); public Ctverec vytvorCtverec(); } public class Tovarna2D implements Tovarna { public Kruh vytvorKruh() { return new Kruh2D(); } public Ctverec vytvorCtverec() { return new Ctverec2D();

Page 53: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

53

} } public class Tovarna3D implements Tovarna { public Kruh vytvorKruh() { return new Kruh3D(); } public Ctverec vytvorCtverec() { return new Ctverec3D(); } }

Následuje třída Klient s jejími vazbami, které jsou uvedeny hned v úvodu. public class Klient { private Tovarna tovarna; private Kruh kruh; private Ctverec ctverec; public Klient(Tovarna tov) { // konstruktor tovarna = tov; kruh = tovarna.vytvorKruh(); ctverec = tovarna.vytvorCtverec(); } public void operace() { kruh.nakresli(); kruh.smaz(); ctverec.nakresli(); ctverec.smaz(); } }

Konstruktoru třídy Klient se předává parametr na třídu Tovarna (abstraktní nebo rozhraní) a vytváří se odpovídající instance tříd Kruh a Ctverec. Metoda operace() pak nakreslí a smaže nejdříve kruh a pak čtverec. Následuje ukázka jednoduché aplikace abstraktní továrny. public class TovarnaKlient { public static void main(String[] args) { Klient klient = new Klient(new Tovarna2D()); klient.operace(); klient = new Klient(new Tovarna3D()); //nastaveni jiné tovarny klient.operace(); } }

Z aplikace je vidět, že při vytváření klienta mu musíme předat instanci konkrétní továrny, podle které bude vytvářet vlastní instance. Pokud změníme parametr (továrnu), budeme dostávat jiné výsledky.

Page 54: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

54

Uplatnění uvedeného návrhového vzoru je výhodné zejména tam, kde potřebujeme vytvořit framework, který bude modifikovatelný na různá prostředí, které v našem jednoduchém příkladu představují klienti. Pak při implementaci stačí pouze nastavit odpovídající továrnu a dostaneme řadu instancí odpovídajícího typu, v našem případě např. prostoru 2D. Pouze změnou parametru továrny se můžeme dostat do prostoru 3D. Ještě si alespoň pouze s využitím diagramu tříd UML uvedeme příklad jednoduché hry, kde je použita také abstraktné továrna. Pokud srovnáte uvedený diagram tříd s předchozím diagramem zabývajícím se kruhy a čtverci, zjistíte velmi nápadnou podobnost.

Obr. 5.4 Diagram tříd počítačové hry využívající vzor abstraktní továrny Tento návrhový vzor obsahuje celkem tři modifikace. Tyto se postupně rozvíjejí. Takže nejdříve je třeba pochopit princip jednoduché továrny a pak teprve postoupit k tovární metodě. Až zvládnete tuto, postoupíte k abstraktné továrně. Vzor Továrna se často objevuje v kódu klienta, pokud je třeba izolovat klienta od přesné znalosti třídy, od které se vytváří instance. Tato potřeba izolace nastává, když rozhodnutí, od které třídy se vytvoří instance, závisí na faktoru, který si klient neuvědomuje. Vzor definuje rozhraní pro zrod objektu na úrovni předka, ale ponechává na

Page 55: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

55

potomkovi rozhodnutí, z jaké konkrétní třídy bude tento objekt (instance) pocházet. Zavádí rozhraní pro tvorbu rodiny souvisejících nebo závislých objektů, přičemž je klient izolován od volání těchto tříd. Tento vzor se jeví jako výhodný tehdy, když potřebujeme odstínit volání konstruktoru (pružnost výměny produktů) a navíc pracujeme s celými „skupinami“ objektů, které potřebujeme přepínat. Nejenže je klient odstíněn od rozhodování, jakou třídu u kterého produktu volit, ale v tomto případě dojde k jednomu „přepnutí“ jednou výměnnou konkrétní továrny za jinou.

Page 56: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

6 Vzor Příkaz (Command)

V této kapitole se dozvíte:

• jak je možné zapouzdř

• jak je možné tyto zapouzd

• jak tyto uložené požadavky vyvolávat.

Po jejím prostudování budete

• porozumět tomutu vzoru a jeho možnostem

• aplikovat tento vzor v

Klí čová slova této kapitoly:

• zapouzdření operace,

Problém: Standardní cesta pro vykonání metody je její vyvolání. Vsituacích nejsme schopni řídit by se měly metody volat. Dalším problémem je flexibilní zpracování návratové hodnoty metody. Kontext: Jak ovládat řadu zařízení povladač měl jednoduché funkce a abychom byli jím ovládaná zařízení (přidat nové, odebrat p Řešení: Oddělení požadavku na danou akci od objetu, který skuteprovede. Návrhový vzor Command práv Vzor si vysvětleme na pcontroller), který bude skutekonkrétních akcí, které zvolíme stiskem Požadavek pak provede konkrétní tpožadovanými operacemi mohou být požzhasnutí světla, zapnutí, vypnutí televizoru. Objekt provádět tuto konkrétní operaci. Bude tcommand pro každé tlačítko vzdáleného ovladavykonání konkrétní operace. Postup je zhruba následující:

1. Klient vytvoří objekt

56

jak je možné zapouzdřit požadavek do formy objektu,

jak je možné tyto zapouzdřené požadavky ukládat,

tyto uložené požadavky vyvolávat.

Po jejím prostudování budete schopni:

t tomutu vzoru a jeho možnostem,

aplikovat tento vzor v návrhu aplikací.

ová slova této kapitoly:

,

Standardní cesta pro vykonání metody je její vyvolání. V některých řídit načasování (timing) kontextu, ve kterém

ly metody volat. Dalším problémem je flexibilní zpracování

ízení přes dálkový ovladač. Požadujeme, aby l jednoduché funkce a abychom byli schopni modifikovat

řidat nové, odebrat původní).

lení požadavku na danou akci od objetu, který skutečně akci provede. Návrhový vzor Command právě oddělí uvedené části od sebe.

na příkladu dálkového ovladače (remote controller), který bude skutečným žadatelem (requester) provedení konkrétních akcí, které zvolíme stiskem daných tlačítek ovladače. Požadavek pak provede konkrétní třída daného zařízení. Jednoduchými požadovanými operacemi mohou být požadavky na rozsvícení,

tla, zapnutí, vypnutí televizoru. Objekt command pak bude t tuto konkrétní operaci. Bude třeba tedy vytvořit řadu objektů

čítko vzdáleného ovladače, které pak zabezpečí e.

Postup je zhruba následující: í objekt command.

Page 57: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

57

2. Klient vykoná metodu setCommand() k uložení objektu command do invokátoru.

3. Později klient požádá invokátor o provedení objektu command. Za zmínku stojí, že poté, co je objekt command nahrán do invokátoru (metodou setCommand()), může být objekt command využit jednou nebo vícekrát, nebo také zrušen či nahrazen jiným objektem command.

Vytvoření prvního objektu command Předtím, než vytvoříme první objekt command, který bude rozsvěcet světlo, deklarujeme nejdříve rozhraní, které bude využívat vzdálený ovladač. public interface Command { public void execute(); }

Jediná metoda rozhraní se jmenuje execute(). Následuje třída „konkrétní“ Command (LightOnCommand), která implementuje rozhraní Command. public class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { // konstruktor this.light = light; } public void execute() { light.on(); } } Vidíme, že třída deklaruje datový atribut light (světlo), který je nastaven v konstruktoru. Implementace metody exekute() pak vyvolá metodu on() třídy Light, čímž se rozsvítí světlo. Třída Light (světlo) má jistě i metodu off(), kterou se světlo zhasne. public class Light { // konstruktor public Light() { } public void on() { System.out.println("Light is on"); } public void off() { System.out.println("Light is off"); } }

Page 58: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

58

Použití objektu command Předpokládejme, že máme dálkový ovladač s jedním tlačítkem a odpovídajícím místem (pozicí, slotem) pro zařízení, které budeme dálkovým ovladačem řídít. public class SimpleRemoteControl { Command slot; // místo pro zařízení public SimpleRemoteControl() {} // konstruktor public void setCommand(Command command) { slot = command; } public void buttonWasPressed() { slot.execute(); } } Metoda setCommand() nastavuje konkrétné objekt command do proměnné slot. Při změně objektu command, musíme tuto metodu vyvolat znova. Metoda buttonWasPressed() je vyvolána vždy při stisku tlačítka. Jednoduchý test funkčnosti je pak následující: public class RemoteControlTest { public static void main(String[] args) { SimpleRemoteControl remote = new SimpleRemoteControl(); // objekt light je příjemcem požadavku Light light = new Light(); LightOnCommand lightOn = new LightOnCommand(light); remote.setCommand(lightOn); remote.buttonWasPressed(); } } Třída RemoteControlTest představuje klienta ve vzoru Command. Objekt remote je náš invoker (ten, kdo se něčeho dovolává) a je mu předán konkrétní objekt commanderu v metodě setCommand(). Vzor Command – definování Vzor Command zapouzdřuje pořadavek (request) jako objekt a tím nám dovoluje parametrizovat další objekty s odlišnými požadavky, frontovat nebo zaznamenávat požadavky a podporovat operaci undo (zrušení provedené operace). Víme, že objekt command svazuje dohromady množinu akcí na konkrétním příjemci. Příjemcem bylo světlo, ale mohou to být např.

Page 59: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

59

garážová vrata, u kterých můžeme požadovat akce: zavření, otevření, zastavení, zhasnutí světla v garáži, rozsvícení svštla v garáži. Abychom toho dosáhli, vzor pakuje akce a příjemce do jednoho objektu, který vystavuje jednu metodu execute(). Když je objekt vyvolán, metoda exekute() způsobí vyvolání akcí na příjemci. Z vnějšího pohledu žádné jiné objekty skutečně neví, jaké akce jsou na příjemci vykonány; tyto objekty pouze ví, že když vyvolají metodu execute(), jejich požadavek bude obsloužen. Podíváme se blíže na garáž. Vidíme, že se jedná ceklem o pět akcí. Musíme proto doplnit odpovídající objekty command pro každou akci. public class GarageDoor { public GarageDoor() { } public void up() { System.out.println("Garage Door is Open"); } public void down() { System.out.println("Garage Door is Closed"); } public void stop() { System.out.println("Garage Door is Stopped"); } public void lightOn() { System.out.println("Garage light is on"); } public void lightOff() { System.out.println("Garage light is off"); } }

Objekt command pro otevření garáže. Ty bychom samozřejmě museli všechny doplnit. public class GarageDoorOpenCommand implements Command { GarageDoor garageDoor; public GarageDoorOpenCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute() { garageDoor.up(); } }

Ukázka testování: public class RemoteControlTest { public static void main(String[] args) {

Page 60: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

60

SimpleRemoteControl remote = new SimpleRemoteControl(); GarageDoor garageDoor = new GarageDoor(); GarageDoorOpenCommand garageOpen = new GarageDoorOpenCommand(garageDoor); remote.setCommand(garageOpen); remote.buttonWasPressed(); } } Vzor Command vyjádřený diagramem tříd UML.

Obr. 6.1 Diagram tříd UML vzoru Command Rozhraní Command je rozhraní v našem případě zatím pouze s metodou execute(). Třída ConcreteCommand je v našich příkladech reprezentovaná třídami LightOnCommand a GarageDoorOpenCommand. Třídu Receiver (příjemce) reprezentují konkrétní třídy Light (světlo) a GarageDoor. Třída Invoker s metodou setCommand() je reprezentovaná třídou SimpleRemoteControl. Třída Client je reprezentovaná třídou s hlavní metodou, tedy třídou RemoteControlTest. Třída Client je zodpovědná za vytváření objektů tříd ConcreteCommand a nastavení jejich příjemce (receiver). Třída Invoker uchovává objekt command a v daném místě žádá objekt command o provedení požadavku, vyvoláním jeho metody execute(). Třída ConcreteCommand definuje vazbu mezi akcí a příjemcem. Invoker vyvolá požadavek vyvoláním metody execute() a objekt concreteCommand to vykoná vyvoláním jedné nebo více akcí na příjemci. Příjemce (receiver) zná, jak provést konkrétní požadavek. Jako příjemce může sloužit libovolná třída.

Page 61: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

61

Nyní se podíváme, jak bychom řešili vzdálený ovladač pro sedm zařízení. Pokud budeme ovládat např. světlo v kuchyni a světlo v obývacím pokoji, nutně potřebujeme dva konkrétní objekty command, jeden pro světlo v kuchyni a druhý pro světlo v obývacím pokoji. Vzor Command to neumí rozlišit. Další věc, kterou musíme zavést, jsou pole pro nastavení jednotlivých zařízení. Tato pole povedeme dvoje a to pro zapnutí a vypnutí, což jsou nejzákladnější operace. Implementace dálkového ovladače public class RemoteControl { Command[] onCommands; // pole pro zapnutí Command[] offCommands; // pole pro vypnutí public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for (int i = 0; i < 7; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); } public String toString() { StringBuffer stringBuff = new StringBuffer(); stringBuff.append("\n---- Remote Control -----\n"); for (int i = 0; i < onCommands.length; i++) { stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n"); } return stringBuff.toString(); } } Vzdálený ovladač ovládá sedm jednoduchých zařízení (zapnutí / vypnutí). Na začátku třídy nastavíme všechny přepínače na objekt

Page 62: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

62

noCommand, jehož třídu uvádíme. To znamená, že pokud nějaký přepínač nebude definovaný, implicitně bude jako noCommand, tedy prázdná metoda execute(). public class NoCommand implements Command { public void execute() { } }

Metoda setCommand() má tři parametry pro uchování všech informací. Prvním parametrem je slot, který tvoří index pro uložení informací v polích. Další dva parametry jsou pro konkrétní objekty command pro zapnutí a pro vypnutí. Metoda toString() vypisuje všechna nastavení této třídy. Třída StereoOnWithCDCommand může mít pak následný kód: public class StereoOnWithCDCommand implements Command { Stereo stereo; public StereoOnWithCDCommand(Stereo stereo) { this.stereo = stereo; } public void execute() { stereo.on(); stereo.setCD(); stereo.setVolume(11); } }

Tato třída konkrétního commandu spouští stereo, spouští CD přehravač s hlasitistí 11. Třída příjemce (receiver) má následující kód. public class Stereo { String location; public Stereo(String location) { this.location = location; } public void on() { System.out.println(location + " stereo is on"); } public void off() { System.out.println(location + " stereo is off"); } public void setCD() { System.out.println(location + " stereo is set for CD input"); } public void setDVD() { System.out.println(location +

Page 63: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

63

" stereo is set for DVD input"); } public void setRadio() { System.out.println(location + " stereo is set for Radio"); } public void setVolume(int volume) { // code to set the volume // valid range: 1-11 (after all 11 is better than // 10, right?) System.out.println(location + " Stereo volume set to " + volume); } }

K otestování našeho postupu pak bude sloužit třída RemoteLoader. public class RemoteLoader { public static void main(String[] args) { RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); Light kitchenLight = new Light("Kitchen"); CeilingFan ceilingFan= new CeilingFan("Living Room"); GarageDoor garageDoor = new GarageDoor(""); Stereo stereo = new Stereo("Living Room"); LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight); LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight); CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan); CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan); GarageDoorUpCommand garageDoorUp = new GarageDoorUpCommand(garageDoor); GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor); StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(stereo); StereoOffCommand stereoOff = new StereoOffCommand(stereo); remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);

Page 64: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

64

remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff); remoteControl.setCommand(3, stereoOnWithCD, stereoOff); System.out.println(remoteControl); remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); remoteControl.onButtonWasPushed(1); remoteControl.offButtonWasPushed(1); remoteControl.onButtonWasPushed(2); remoteControl.offButtonWasPushed(2); remoteControl.onButtonWasPushed(3); remoteControl.offButtonWasPushed(3); } } Jak jste si všimli, objekt třídy NoCommand je příkladem null objektu. Ten také slouží k návratu objektu z metody, když „nemáme“ co poslat zpět. Přidání metody undo do rozhraní Command Když rozhraní Command podporuje metodu undo, musíme ji doplnit nejdříve do rozhraní. public interface Command { public void execute(); public void undo(); } Teď se podíváme, jak budeme metodu undo() implementovat do tříd konkrétních commandů. Ukážeme si to na příkladu rozsvícení a zhasnutí světla. public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } public void undo() { light.off(); } } public class LivingroomLightOffCommand implements Command { Light light;

Page 65: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

65

public LivingroomLightOffCommand(Light light) { this.light = light; } public void execute() { light.off(); } public void undo() { light.on(); } } Vidíme, že operace on() a off() jsou opačné. Ještě musíme opravit třídu RemoteControl. public class RemoteControl { Command[] onCommands; Command[] offCommands; Command undoCommand; public RemoteControl() { onCommands = new Command[7]; offCommands = new Command[7]; Command noCommand = new NoCommand(); for(int i=0;i<7;i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } undoCommand = noCommand; } public void setCommand(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onButtonWasPushed(int slot) { onCommands[slot].execute(); undoCommand = onCommands[slot]; } public void offButtonWasPushed(int slot) { offCommands[slot].execute(); undoCommand = offCommands[slot]; } public void undoButtonWasPushed() { undoCommand.undo(); } public String toString() { StringBuffer stringBuff = new StringBuffer(); stringBuff.append("\n---- Remote Control -----\n"); for (int i = 0; i < onCommands.length; i++) { stringBuff.append("[slot " + i + "] " +

Page 66: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

66

onCommands[i].getClass().getName() + " " + offCommands[i].getClass().getName() + "\n"); } stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n"); return stringBuff.toString(); } } Objekty undoCommand jsou zvýrazněny žlutě. public class RemoteLoaderTest { public static void main(String[] args) { RemoteControl remoteControl = new RemoteControl(); Light livingRoomLight = new Light("Living Room"); LightOnCommand livingRoomLightOn =

new LightOnCommand(livingRoomLight); LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight); remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff); remoteControl.onButtonWasPushed(0); remoteControl.offButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); remoteControl.offButtonWasPushed(0); remoteControl.onButtonWasPushed(0); System.out.println(remoteControl); remoteControl.undoButtonWasPushed(); } }

Objekty command pak ještě mohou být ukládány do kolekcí a pak spouštěny najednou. Tento vzor má řadu uplatnění, např. ošetřování chybových stavů, práce s událostmi řízených programech. Operace, která se má provést je „zabalena do objektu“ a pak se s ní dá pružně manipulovat. Klient je odstíněn od konkrétní implementace metody execute(). Konkrétní metoda je vybrána podle typu objektu, který metodu execute() aktuálně vyvolává. Navíc jsme viděli, jak se dá připojit objekt třídy noCommand a metoda undo().

Page 67: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

7 Vzor Adaptér a

V této kapitole se dozvíte:

• jak je možné zesouladit dv

vykonávají podobné operace,

• jak usnadnit práci se systémem, který má celou

možností.

Po jejím prostudování budete schopn

• aplikovat vzor

aplikacích.

Klí čová slova této kapitoly:

• vzor adaptér, vzor fasáda

7.1 Adapt

Kontext: Při vývoji aplikace je k dispozici ndo existujících struktur Problém:Jak využít existující tJejí rozhraní je jiné, než o Řešení: Zavést objekt jako spojku mezi klienta, který onekompatibilnneznámé rozhranírozhraní, které orozhraním Nejdříve si uvedeme, jak by vypadal adapter pro tměla odpovídat rozhraní Kachna. Uvedeme proto jak rozhraní, tak i zmíněné tř public interface public

public

} public class public

Vzor Adaptér a Fasáda (Adapter, Facade)

této kapitole se dozvíte:

jak je možné zesouladit dvě odlišná rozhraní,

vykonávají podobné operace,

jak usnadnit práci se systémem, který má celou

možností.

Po jejím prostudování budete schopni:

aplikovat vzor adaptér a fasáda ve svých návrzích nebo

aplikacích.

ová slova této kapitoly:

vzor adaptér, vzor fasáda

7.1 Adaptér

i vývoji aplikace je k dispozici nějaká třída, ale tato t

do existujících struktur – třída nepodporuje existující rozhraní

: ak využít existující třídu, která nějak nezapadá do existující struktury

Její rozhraní je jiné, než očekáváme.

avést objekt jako spojku mezi klienta, který očekává urnekompatibilní rozhraní dané třídy, takže klient mneznámé rozhraní. Záměrem vzoru Adaptér je poskytnout (zprozhraní, které očekává klient, zatímco používá služby trozhraním.

říve si uvedeme, jak by vypadal adapter pro třídula odpovídat rozhraní Kachna. Uvedeme proto jak rozhraní, tak i ěné třídy.

interface Kachna { public void kvakani(); public void letani();

class DivokaKachna implements Kachna { public void kvakani() {

System.out.println("Kvakam");

67

které principiálně

jak usnadnit práci se systémem, který má celou řadu nastavení a

adaptér a fasáda ve svých návrzích nebo

ída, ale tato třída „nepasuje“ ída nepodporuje existující rozhraní.

jak nezapadá do existující struktury.

ekává určité rozhraní a ídy, takže klient může využívat

r je poskytnout (zpřístupnit) zatímco používá služby třídy s odlišným

řídu Krocan, která by la odpovídat rozhraní Kachna. Uvedeme proto jak rozhraní, tak i

Page 68: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

68

} public void letani() { System.out.println("Letim"); } }

Rozhraní Kachna a její implementace. public interface Krocan { public void hudrovani(); public void letani(); } public class DivokyKrocan implements Krocan { public void hudrovani() { System.out.println("Hudry hudry"); } public void letani() { System.out.println("Letam kratke vzdalenosti"); } }

Nyní může nastat situace, kdy máme nedostatek objektů třídy Kachna a potřebujeme je nahradit objekty třídy Krocan. Jak je vidět z kódu, není to možné provést přímo, protože obě třídy mají odlišná rozhraní. Musíme vytvořit třídu KrocanAdapter, která dá do souladu obě rozhraní. public class KrocanAdapter implements Kachna { private Krocan krocan; public KrocanAdapter(Krocan krocan) { this.krocan = krocan; } public void kvakani() { krocan.hudrovani(); } public void letani() { for(int i=0; i < 5; i++) { krocan.letani(); } } } Třída KrocanAdapter má datový akribut rozhraní Krocan, který je nastaven v konstruktoru. Tato třída má metody shodné s rozhraním Kachna, ale těla metod jsou odlišná. To nám nevadí, protože potřebujeme dát do souladu rozhraní. V metodě kvákání() vidíme, že se převede na hudrování a v metodě letani je cyklus pro létání, aby se krocan, který létá málo se trochu vyrovnal kachně. Uvedeme ještě kód pro ověření funkčnosti.

Page 69: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

69

public class KachnaTest { public static void main(String[] args) { DivokaKachna kachna = new DivokaKachna(); DivokyKrocan krocan = new DivokyKrocan(); Kachna turkeyAdapter = new KrocanAdapter(krocan); System.out.println("Krocan rika..."); krocan.hudrovani(); krocan.letani(); System.out.println("\nKachna rika..."); testDuck(kachna); // ověření kachny System.out.println("\nKrocanAdapter rika..."); testDuck(turkeyAdapter); // ověření adapteru } static void testDuck(Kachna kachna) { kachna.kvakani(); kachna.letani(); } }

Vzor adapter převádí rozhraní třídy na jiné rozhraní, které očekává klient. Adatper dovoluje třídám pracovat společně, což by bez něho nebylo možné.

Obr. 7.1 Jednoduchý adapter – diagram tříd UML (objektový adapter) V uvedeném diagramu třída Client představuje třídu KachnaTest, rozhraní Target představuje rozhraní Kachna, Adapter je třída KrocanAdapter a Adaptee je třída Krocan. Třída Adapter (KrocanAdapter) v sobě obsahuje datový akribut Krocan, je tedy složena z Adaptee (Krocana). Z diagramu tříd vidíme (stejně jako z předchozího kódu), že adapter implementuje rozhraní a ne konkrétní třídu, což zvyšuje jeho flexibilitu (KrocanAdapter implementuje rozhraní Kachna a ne konkrétní třídu DivokaKachna). Tento typ adapteru, kdy adapter je složen z Adaptee

Page 70: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

70

nese označení objektový adapter. Využívá se při něm již zmíněné skládání objektů. Existuje ještě druhá varianta, a to tzv. třídní (Class) adapter, kdy požadované vlastnosti adapteru je dosaženo pomocí dědičnosti.

Obr. 7.2 Třídní adapter (Class adapter). Z diagramu tříd vidíme, že třídní adapter vyžaduje vícenásobnou dědičnost, což v Javě není možné. Jedinou možností by bylo ponechat Target jako rozhraní, a pak bychom to mohli realizovat. Adaptace enumeration na iterator. Uvedeme ještě další, reálnější příklad na využití vzoru Adaptér. Jedná se o dvě odlišná rozhraní pro procházení kolekcemi dat. Rozhraní obou možností procházení kolekcemi je následující:

hasNext()

next()

remove()

«interface»

Iterator

hasMoreElements()

nextElement()

«interface»

Enumeration

Obr. 7.3 Rozhraní pro procházení kolekcemi Rozhraní Adaptee představuje Enumeration a rozhraní Target představuje Iterator. To znamená, že požadujeme, aby se rozhraní Enumeration adaptovalo na nové rozhraní Iterator. Dříve se kolekce v Javě procházely pomocí rozhraní Enumetation. Metoda elements() vrací Enumeration (rozhraní). Rozhraní Enumeration dovoluje procházet prvky kolekce, bez nutnosti znalosti specifik, jak jsou prvky řízeny v kolekci. Návrh řešení je uveden na následujícím diagramu tříd.

Page 71: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

71

Obr. 7.4 Adaptor pro převod rozhraní Enumeration na rozhraní Iterator. Třída EnumerationIterator představuje Adapter pro řešenou úlohu. Samozřejmě třída, která implementuje rozhraní Enumeration, je pak třídou Adaptee. Když se podíváme na obě rozhraní uvidíme, že problém nastane u rozhraní Enumeration, které nedeklaruje metodu remove(). Neexistuje způsob, jak implementovat plnou funkčnost právě z důvodu metody remove(). Jediná možnost jak problém vyřešit je vyhodit výjimku runtime. Tvůrci rozhraní ale počítali s tím, že ne všechny kolekce budou schopny implementovat tuto metodu a proto již sami zavedli UnsupportedOperationException, tedy výjimku přímo pro tento případ. Následuje kód adapteru EnumerationIterator public class EnumerationIterator implements Iterator { Enumeration enumeration; public EnumerationIterator(Enumeration enumeration) { this.enumeration = enumeration; } public boolean hasNext() { return enumeration.hasMoreElements(); } public Object next() { return enumeration.nextElement(); } public void remove() { throw new UnsupportedOperationException(); } }

Page 72: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

72

Kód je podobný jako u předchozího adapteru. Samozřejmě můžeme požadovat i opačný adapter, to znamená ten, který bude adaptovat rozhraní Iterator na rozhraní Enumaretion. To je ale úloha pro vás. Adaptace dat pro JTable Příklady použití vzoru Adapter bychom našli ve standardní knihovně javovských programů. Třída JTable patří do balíčku javax.swing a princip její práce vychází ze vzoru MVC (viz kapitola 3.1) s tím, že controller a view jsou v jedné komponentě JTable a model je v komponentě TableModel. TableModel uvědomuje o změnách komponentu view v JTable. JTable (komponenta Controller) modifikuje data v komponentě TableModel Komponenta TableModel představuje adapter, se kterým komunikuje klient. Jeho požadavky se pak realizují v komponentě Jtable. Uvedené řešení využívá jak vzoru model-view-controller (MVC), tak vzoru Adapter. JTable • Implementuje architekturu delegat-model. • Má pravomoc pro TableModels. • Modifikuje datové atributy TableModelu (modify). TableModel • Deklaruje metody. • Obnovuje modifikovaná data. • Uvědomuje TableModel o změnách (notify).

Obr. 7.5 JTable a TableModel Vzor Adapter umožňuje, aby existující třída splnila požadavky třídy Klient. Pokud klient specifikuje požadavky v rozhraní, vytvoří se nová třída (adapter), která implementuje požadavky rozhraní a zároveň je podtřídou existující třídy – Class Adapter. Pokud klient nespecifikuje své požadavky přes rozhraní, vytvoří se podtřída od klienta, jejímž datovým atributem bude instance existující třídy – Object Adapter. Komponenta JTable s balíčku javax.swing využívá vzor Adapter. Adapter se většinou vytvoří jako podtřída od AbstractTableModel.

notifies

modifies

JTable TableModel

Page 73: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

73

7.2 Vzor Fasáda (Facade) Kontext: Velkou výhodou objektově orientovaného programování je, že pomáhá k tomu, aby aplikace nebyly od počátku monolitické. Ideálně v objektově orientované aplikaci ne příliš rozsáhlá třída využívá ke splnění požadavků aplikace znovupoužitelné knihovní balíčky tříd. Problém: Znovapoužitelnost takovýchto balíčků se stává problémem v tom, že snaha po široké aplikovatelnosti tříd obsažených v knihovních balíčcích vede k tomu, že tyto třídy mohou poskytovat ohromné množství variant a možností, které je třeba určit (zadat jako parametry) před jejich použitím. Řešení: Jistou pomocí může být vlastní vývojové prostředí (integrated development environment IDE), které může uživatele izolovat od rozsáhlých možností výběru. Jinou možností, která pomůže zjednodušit využití knihovních tříd je návrhový vzor Fasáda. Fasáda je třída s úrovní funkcionality, která leží mezi třídami knihoven a kompletní aplikací a poskytuje jednoduché použití těchto tříd uložených v balíčcích. Záměrem vzoru Fasáda je poskytnout rozhraní, které umožní snadné využívání znovupoužitelných tříd. S tímto vzorem jsou spojeny další odvozené názvy s poněkud odlišným významem. Pokud bude mít vzor Fasáda všechny metody statické (třídní), nazývá se utilitou. Demo je dalším trochu odlišným příkladem vzoru Fasáda. Rozdíly jsou především v tom, že demo je obyčejně samostatná aplikace, která zpravidla obsahuje i potřebná data. Vzor Fasáda je navíc obyčejně konfigurovatelný a vytvořený se záměrem dalšího znovupoužití. Návrhový vzor si vysvětlíme na příkladu budování „domácího kina“. Jedná se o systém, který se skládá z řady elektrických (elektronických) zařízení, které jsou mezi sebou spojeny a navíc mají různé možnosti spuštění a práce (ideální pro vzor fasáda). Jednotlivá zařízení budou představovat třídy. Takže se můžeme podívat na obrázek aktuálního stavu. Na diagramu tříd vidíme jednak datové atributy jednotlivých tříd a také metody, které můžeme využívat. Dále uvádíme postup, který musíme provést, když se chceme dívat na domácí kino.

1. Turn on popcorn popper (zapnout popkornovou pánev).

2. Start the popper popping (začít dělat popkorn).

3. Dim the lights (ztlumit světla).

4. Put the screen down (stáhnout plátno).

Page 74: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

74

5. Turn the projector on (zapnout projektor).

6. Set the projector input to DVD (vstup projektoru do DVD).

7. Put the projector on wide-screen mode (nastavit u projektoru mód wide-screen).

8. Turn the sound amplifier on (zapnout zesilovač)

9. Set the amplifier to DVD input (napojit zesilovač na vstup DVD)

10. Set the amplifier to surround sound (nastavit zesilovač na surround efekt)

11. Set the amplifier volume to Projector (nastavit hlasitost zesilovače na projektor)

12. Turn the DVD Player on (zapnout DVD player)

13. Start the DVD Player playing (spustit DVD player)

Obr. 7.6 Diagram tříd UML „domácího kino“ Dále je uveden kód metod, které musíme provést při tom, když se chceme dívat na domácí kino. Z kódu vidíme, že se jedná o objekty různých tříd, na které musíme někdy aplikovat více metod.

Page 75: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

75

popper.on(); popper.pop(); lights.dim(); screen.down(); projector.on(); projector.setInput(dvd); projector.wideScreenMode(); amp.on(); amp.setDvd(dvd); amp.setSurroundSound(); dvd.on(); dvd.play(movies);

Další věci, které si musíme uvědomit je, že při vypínání je to třeba dělat znovu většinou v obráceném pořadí. Zde se nabízí otázka, zda to není příliš složité poslouchat CD nebo rádio? Další problém nastane, když systém budeme postupně zaměňovat za novější (budeme provádět upgrade systému). Návrhový vzor Fasáda je právě to, co potřebujeme. Třída Fasáda poskytuje jedno rozumné rozhraní.

Page 76: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

76

Obr. 7.7 Diagram tříd návrhového vzoru Fasáda. Fasáda nejen zjednodušuje rozhraní, ale také odděluje klienta od subsystému komponent. Fasády a adaptery mohou zabalit mnoho tříd, ale záměrem vzoru Fasáda je zjednodušit rozhraní pro klienta, zatímco Adaptér převádí (konvertuje) dané rozhraní na jiné. Programová konstrukce Fasády je následující. Třída HomeTheatreFacade obsahuje datové atrubuty všech zařízení, ze kterých se skládá. Jedná se o agregaci a datové atrubuty jsou naplněny v konstruktoru uvedené třídy.

Page 77: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

77

public class HomeTheaterFacade { Amplifier amp; Tuner tuner; DvdPlayer dvd; CdPlayer cd; Projector projector; TheaterLights lights; Screen screen; PopcornPopper popper; public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, Screen screen, TheaterLights lights, PopcornPopper popper) { this.amp = amp; this.tuner = tuner; this.dvd = dvd; this.cd = cd; this.projector = projector; this.screen = screen; this.lights = lights; this.popper = popper; } . . . // další kód }

Jednotné rozhraní pro metody watchMovie() (sledování domácího kina) a endMovie() (ukončení domácího kina) pak mají následující kód: public void watchMovie(String movie) { System.out.println("Get ready to watch a movie..."); popper.on(); popper.pop(); lights.dim(10); screen.down(); projector.on(); projector.wideScreenMode(); amp.on(); amp.setDvd(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); dvd.play(movie); } public void endMovie() { System.out.println("Shutting movie theater “ + “ down..."); popper.off(); lights.on(); screen.up(); projector.off(); amp.off(); dvd.stop(); dvd.eject(); dvd.off();

Page 78: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

78

} Jednoduchý kód na odzkoušení vzoru Fasáda je pak následující. V kódu jsou uvedeny dvě dříve zmíněné metody sledování domácího kina aukončení sledování domácího kina. public class HomeTheaterTestDrive { public static void main(String[] args) { Amplifier amp = new Amplifier("Top-O-Line Amplifier"); Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp); DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp); CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp); Projector projector = new Projector("Top-O-Line Projector", dvd); TheaterLights lights = new TheaterLights("Theater Ceiling Lights"); Screen screen = new Screen("Theater Screen"); PopcornPopper popper = new PopcornPopper("Popcorn Popper"); HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, screen, lights, popper); homeTheater.watchMovie("Raiders of the Lost Ark"); homeTheater.endMovie(); } } Kdybychom měli nyní stručně charakterizovat vzor Fasáda řekli bychom, že vzor Fasáda poskytuje jednotné rozhraní pro množinu rozhraní v subsystému. Fasáda definuje vyšší úroveň rozhraní, která usnadňuje využití subsystému. Toto vyjádření bychom mohli graficky znázornit následovně:

Page 79: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

79

Obr. 7.8 Schematické znázornění vzoru fasáda. Možností, která pomůže zjednodušit využití tříd knihoven je návrhový vzor Fasáda. Fasáde je třída s úrovní funkcionality, která leží mezi třídami knihoven a kompletní aplikací a poskytuje jednoduché použití těchto tříd uložených v balíčcích. Záměrem vzoru Fasáde je poskytnout rozhraní, které umožní snadné využívání znovupoužitelných tříd. S tímto vzorem jsou spojeny další odvozené názvy s poněkud odlišným významem. Pokud bude mít vzor Facade všechny metody statické (třídní), nazývá se utilitou. Demo je dalším trochu odlišným případem vzoru Facade. Demo se ale liší v tom, že je to obyčejně samostatná aplikace, která obyčejně obsahuje i potřebná data.

Page 80: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

8 Vzor Iterátor a Skladba (Iterator, Composite)

V této kapitole se dozvíte:

• jak je možné snadno a jednoduše procházet sekvenkolekcemi,

• jak ve stromových strukturách umožnit jednotné zacházení s koncovými objekty (listy) a

Po jejím prostudování budete schopni:

• využívat rozhraní Iterator,

• definovat a implementovat vlastní Iterátor,

• využívat stromové struktury pro ukládání dat.

Klí čová slova této kapitoly:

• kompozice celek část, iterátor, stromová struktura

8.1 Iterátor Existuje mnoho způsobů, jak ukládat objekty do kolekcí. Konkrétními implementacemi pro ukládání mohou být: Hashtable. Všechny tyto implementace mají své výhody a nevýhody pro ukládání dat. Podle konkrétních požadavktu nejvhodnější implementaci. Problém: Univerzální (generické) sekven Kontext: Seznamy objektů a jejich snadné Řešení: Zavedení rozhraní se základními operacemi remove(). Jedna operace je však pro všechny možné implemenatce pro ukládání dat společná a tou je sekvenčmůžeme vyhledávat dané prvky, nebo je mnebo nějak upravovat. K této for nebo while resp. repeat. Podívejme se ale na problém procházení kolekcemi trochu podrobněji. Jako pmenu ve dvou různých restauracích. I když vpoužívaji stejnou deklaraci položky na metřídy pro popis položky na menu je následující:

80

Vzor Iterátor a Skladba (Iterator, Composite)

jak je možné snadno a jednoduše procházet sekvenčně

ve stromových strukturách umožnit jednotné zacházení koncovými objekty (listy) a se složenými objekty (uzly).

Po jejím prostudování budete schopni:

využívat rozhraní Iterator,

a implementovat vlastní Iterátor,

využívat stromové struktury pro ukládání dat.

ová slova této kapitoly:

část, iterátor, stromová struktura

ů, jak ukládat objekty do kolekcí. Konkrétními implementacemi pro ukládání mohou být: ArrayList, Stack, LinkedList,

. Všechny tyto implementace mají své výhody a nevýhody pro ukládání dat. Podle konkrétních požadavků na uložení si vybereme

jší implementaci.

sekvenční procházení seznamy objektů .

snadné procházení.

avedení rozhraní se základními operacemi hasNext(), next(),

Jedna operace je však pro všechny možné implemenatce pro ukládání sekvenční procházení kolekcemi. Při této operaci

žeme vyhledávat dané prvky, nebo je můžeme filtrovat, tisknout této činnosti jsme doposud používali příkazy

for nebo while resp. repeat. Podívejme se ale na problém procházení ěji. Jako příklad si vybereme problém dvou

zných restauracích. I když v obou restauracích používaji stejnou deklaraci položky na menu (MenuItem). Struktura

ídy pro popis položky na menu je následující:

Page 81: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

81

public class MenuItem { String name; String description; boolean vegetarian; double price; // konstruktor public MenuItem(String name, String description, boolean vegetarian, double price) { this.name = name; this.description = description; this.vegetarian = vegetarian; this.price = price; } public String getName() { return name; } public String getDescription() { return description; } public double getPrice() { return price; } public boolean isVegetarian() { return vegetarian; } public String toString() { return (name + ", $" + price + "\n " + description); } } Třída MenuItem má čtyři atributy, které jsou naplněny v konstruktoru, přístupové metody a metodu toString(). Problém, který budeme řešit je, že restaurace PanCake House ukládá položky menu do kolekce objektu ArrayList (zaměřuje se na snídaňová menu) a restaurace Objectville Diner má položky menu uloženy ve struktuře pole (array). public class PancakeHouseMenu { ArrayList menuItems; // konstruktor public PancakeHouseMenu() { menuItems = new ArrayList(); addItem("K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99); addItem("Regular Pancake Breakfast", "Pancakes with fried eggs, sausage",

Page 82: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

82

false, 2.99); addItem("Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49); addItem("Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59); } public void addItem(String name, String description, boolean vegetarian, double price) { MenuItem menuItem = new MenuItem(name, description, vegetarian, price); menuItems.add(menuItem); } // deklarace ostatních metod . . . }

Programový kód třídy DinerMenu je následující: public class DinerMenu { static final int MAX_ITEMS = 6; int numberOfItems = 0; MenuItem[] menuItems; // konstruktor public DinerMenu() { menuItems = new MenuItem[MAX_ITEMS]; addItem("Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99); addItem("BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99); addItem("Soup of the day", "Soup of the day, with a side of potato salad", false, 3.29); addItem("Hotdog", "A hot dog, with saurkraut, relish, onions", false, 3.05); addItem("Steamed Veggies and Brown Rice", "Steamed vegetables over brown rice", true, 3.99); addItem("Pasta", "Spaghetti with Marinara Sauce", true, 3.89); } public void addItem(String name, String description, boolean vegetarian, double price) { MenuItem menuItem = new MenuItem(name, description, vegetarian, price); if (numberOfItems >= MAX_ITEMS) {

Page 83: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

83

System.err.println( "Sorry, menu is full! Can't add item to menu"); } else { menuItems[numberOfItems] = menuItem; numberOfItems = numberOfItems + 1; } } public MenuItem[] getMenuItems() { return menuItems; } // other menu methods here } Máme zde problém dvou odlišných reprezentací menu. Budeme předpokládat, že se obě restaurace spojí a budou mít odlišné specifikace pro menu. Jak bude vypadat javovská specifikace pro servírku? Servírka bude potřebovat následující operace (metody): printMenu() - tiskne každou položku na menu (snídaně i obědy) printBreakfast() - tiskne pouze položky menu snídaně printLunchMenu() - tiskne pouze položky menu oběd printVegetarianMenu() - tiskne všechny položky z obou menu pro vegetariány isItemVegetarian(name) - parametr název pokrmu, vrací true nebo false K tisku všech položek se zavolá getMenuItem() z PancakeHouseMenu a DinerMenu. Obě vrací různý typ: PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu(); ArrayList breakfastItems = pancakeHouseMenu.getMenuItems(); DinnerMenu dinnerMenu = new DinnerMenu(); menuItem[] lunchItems = dinnerMenu.getMenuItems(); Pokud budeme sledovat vnitřní kód metody printMenu() uvidíme, že tato metoda se bude vyskytovat ve dvou variantách, podle typu uložení dat:

Page 84: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

84

// ArrayList for (int I = 0; i < breakfastItems.size(); i++) { MenuItem menuItem = (MenuItem)breakfastItems.get(i); System.out.print(menuItem.getName() + “ “); System.out.println(menuItem.getPrice() + “ “); System.out.println(menuItem.getDescription()); } // Array pole for (int I = 0; I < lunchItems.length; i++) { MenuItem menuItem = lunchItems[i]; System.out.print(menuItem.getName() + “ “); System.out.println(menuItem.getPrice() + “ “); System.out.println(menuItem.getDescription()); }

Řádek s modrým písmem ukazuje na rozdíly v logice kódu. Podobně to bude vypadat i pro ostatní metody, které požaduje servírka. Problémem tedy zůstává, že musíme udržovat různé metody, které obsluhují stejné činnosti. Shrnutí:

• Obě menu jsme implementovali konkrétně – bez rozhraní. • Servírka (Waitress) neimplementuje Java API Waitress,

nedodržuje standard. • Přidáme-li další menu využívající např. Hashtable, musíme

modifikovat hodně kódu ve třídě Waitress. • Třída Waitress potřebuje vědět, jak každé menu reprezentuje

svoji vnitřní kolekci položek, to porušuje pravidla zapouzdření. • Duplikovali jsme kód: metoda printMenu() potřebuje dva

oddělené cykly pro iteraci nad dvěma různými druhy menu. • Přidáme-li další menu, potřebujeme zavést další cyklus. • Implementace není založena na MXML (Menu XML) a není tedy

interoperabilní, jak by měla být. Vyvstává otázka, zda se dá zapouzdřit itrerace (procházení kolekcí). Budeme vycházet z principu, že se má zapouzdřit vše co se mění. V našem případě se mění iterace způsobená různým druhem kolekcí objektů, které jsou vráceny z menu. Bližší pohled na problém: 1) K iteraci přes položky breakfast používáme metody size() a get() ArrayList: for (int i = 0; i < breakfastItems.size(); i++) { MenuItem menuItem = (MenuItem)breakfastItems.get(i); get(0), get(1), get(2), get(3)

Page 85: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

85

2) K iteraci přes položky lunch, využíváme zabudované možnosti array, to znamená jeho atribut lenght a přístup k položkám pole: array - pole for (int i = 0; i < lunchItems.length; i++) { MenuItem menuItem = lunchItems[i]; lunchItems[1], lunchItems[1], lunchItems[2], lunchItems[3]

Objekt Iterátor v kolekci ArrayList a Array Našim cílem je vytvořit objekt, který nazveme Iterator a který zapouzdřuje způsob, kterým procházíme přes kolekci objektů. Iterátor pro ArrayList: metoda next() provádí: metody get(0), get(1), get(2) … Iterator iterator = breakfastMenu.createIterator(); while (iterator.hasNext()) { MenuItem menuItem = (MenuItem) iterator.next();

Iterátor pro Array: metoda next() provádí: metody lunchItems[0], lunchItems[1], lunchItems[2] … Iterator iterator = lunchMenu.createIterator(); while (iterator.hasNext()) { MenuItem menuItem = (MenuItem) iterator.next();

Řešení vzniklého problému je zobrazeno na obr. 8.1. Z obrázku je zřejmé, že Iterátor je deklarovaný jako rozhraní se dvěma metodami, a to metodou hasNext(), která vrací booleovskou hodnotu, podle toho zda v kolekci existuje ještě další položka. Další metoda next() vrací konkrétní položku z kolekce. Tyto metody jsou pak implementovány různě. Uvedený obrázek ukazuje jejich implementaci ve třídě DinnerMenuIterator, která pro uložení položek menu využívá array (pole).

Obr. 8.1 Tvorba Iterátoru Programové řešení uvedené situace následuje: public interface Iterator { boolean hasNext(); Object next(); } public class DinerMenuIterator implements Iterator { MenuItem[] items; int position = 0;

Page 86: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

86

public DinerMenuIterator(MenuItem[] items) { this.items = items; } public Object next() { MenuItem menuItem = items[position]; position = position + 1; return menuItem; } public boolean hasNext() { if (position >= items.length || items[position] == null) { return false; } else { return true; } } } public class DinerMenu implements Menu { static final int MAX_ITEMS = 6; int numberOfItems = 0; MenuItem[] menuItems; public DinerMenu() { // konstruktor . . . } public MenuItem[] getMenuItems() { return menuItems; } public Iterator createIterator() { return new DinerMenuIterator(menuItems); } // ostatní metody menu . . . }

Ještě musíme doplnit rozhraní Menu, které deklaruje metodu createIterator(). Toto rozhraní je využito ve třídě DinerMenu. public interface Menu { public Iterator createIterator(); } Dále potřebujeme integrovat kód iterátoru do třídy servírka (Waitress): public class Waitress { PancakeHouseMenu pancakeHouseMenu; DinerMenu dinerMenu; public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu;

Page 87: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

87

} public void printMenu() { Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator(); System.out.println("MENU\n----\nBREAKFAST"); printMenu(pancakeIterator); System.out.println("\nLUNCH"); printMenu(dinerIterator); } private void printMenu(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print(menuItem.getPrice() + " -- "); System.out.println(menuItem.getDescription()); } } } Z kódu vidíme, že servírka má dva datové atributy na různé druhy menu. V konstruktoru si vytvoříme dva iterátory a ty pak pošleme do metody printMenu(), která nám zabezpečí odpovídající tisk. public class MenuTestDrive { public static void main(String args[]) { PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu(); DinerMenu dinerMenu = new DinerMenu(); Waitress waitress = new Waitress(pancakeHouseMenu, dinerMenu); waitress.printMenu(); } } Třída MenuTestDrive ukazuje, jak můžeme prověřit funkčnost navrženého řešení. Rekapitulace toho, co jsme dosud udělali.

• Přidali jsme PancakeHouseMenuIterator a DinnerMenuIterator.

• Waitress – bude daleko snadněji udržovat a rozšiřovat o další menu.

• Menu nejsou dobře zapouzdřena. • Dinner používá Array • Pancake používá ArrayList

• Potřebujeme dva cykly k průchodu.

Page 88: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

88

• Servírka je svázána se dvěma konkrétními třídami (MenuItem[], ArrayList).

Obr. 8.2 Aktuální stav řešení problému Z předchozího obrázku vyplývá, že PancakeHouseMenu a DinnerMenu implementují stejné rozhraní – chceme to upravit tak, aby Waitress nebyla závislá na konkrétním Menu. Iterátor je zapouzdřený a může existovat i mimo třídu s položkami, které prochází (mimo aggregate).

Abychom mohli pracovat s javovskými knihovnami, musíme implementovat metodu remove(), kterou standardně poskytuje toto rozhraní. Místo vytváření vlastního iterátoru zavoláme iterátor java.util.Iterator.

Obr. 8.3 Rozhraní Iterator deklarované v Javě To znamená, že nemusíme deklarovat metodu createIterator(). Programový kód je pak následovný: import java.util.Iterator; public class DinerMenuIterator implements Iterator { MenuItem[] list; int position = 0; public DinerMenuIterator(MenuItem[] list) { this.list = list; } public Object next() { MenuItem menuItem = list[position]; position = position + 1; return menuItem;

hasNext()

next()

remove()

«interface»

Iterator

Page 89: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

89

} public boolean hasNext() { if (position >= list.length || list[position] == null) { return false; } else { return true; } } public void remove() { if (position <= 0) { throw new IllegalStateException("You can't remove an item until you've done at least one next()"); } if (list[position-1] != null) { for (int i = position-1; i < (list.length-1); i++) { list[i] = list[i+1]; } list[list.length-1] = null; } } } Museli jsme ale implementovat metodu remove(), protože tato metoda je součástí rozhraní Iterator. Na obr. 8.2 je vidět, že servírka (Waitress) je příliš těsně spojena s oběma konkrétními menu. To bude způsobovat komplikace při dalších možných budoucích úpravách. Zavedeme proto rozhraní Menu s metodou createIterator().

Obr. 8.4 Rozšíření aplikace o rozhraní Menu Třída Waitress se může odkazovat na libovolné menu prostřednictvím rozhraní a ne konkrétní třídy. Je tak snížena závislost mezi Waitress a konkrétními třídami – programování k rozhraní a ne k implementaci. Programový kód:

Page 90: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

90

import java.util.Iterator; public class Waitress { Menu pancakeHouseMenu; Menu dinerMenu; public Waitress(Menu pancakeHouseMenu, Menu dinerMenu) { this.pancakeHouseMenu = pancakeHouseMenu; this.dinerMenu = dinerMenu; } public void printMenu() { Iterator pancakeIterator = pancakeHouseMenu.createIterator(); Iterator dinerIterator = dinerMenu.createIterator(); System.out.println("MENU\n----\nBREAKFAST"); printMenu(pancakeIterator); System.out.println("\nLUNCH"); printMenu(dinerIterator); } private void printMenu(Iterator iterator) { while (iterator.hasNext()) { MenuItem menuItem = (MenuItem)iterator.next(); System.out.print(menuItem.getName() + ", "); System.out.print(menuItem.getPrice() + " -- "); System.out.println(menuItem.getDescription()); } } // deklarace dalších metod . . . } Vzor Iterátor dovoluje sekvenční způsob přístupu k prvkům agregovaného objektu bez vystavování jejich vnitřní reprezentace. Iterátor také umisťuje úlohu traverzování na objekt iterátoru, ne na agregovaný objekt, což zjednodušuje rozhraní a implementaci agregovaného objektu a umisťuje zodpovědnost tam, kde by měla být.

Obr. 8.5 Základní struktura vzoru Iterátor

Page 91: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

91

Rozhraní pro agregované objekty je výhodné pro objekt Client. Ten je oddělen od implementace kolekce objektů. Rozhraní Iterator poskytuje rozhraní, které všichni iterátoři musí implementovat a dále poskytuje metody k traverzování kolekce. Každý ConcreteAggregate je zodpovědný za instanciaci konkrétního iterátora, který iteruje přes všechny objekty kolekce.

Myšlenka vzoru Iterátor je zavedení rozhraní s operacemi sekvenčního průchodu strukturou (seznamem, polem) s možností vrácení aktuálního prvku. Klient nebude k původní složité struktuře přistupovat přímo, ale prostřednictvím rozhraní s uvedenými operacemi. Standardní knihovny container v Javě mají již metody implementované. My jsme v naší ukázce nejdříve deklarovali rozhraní a pak implementovali jednotlivé metody, aby bylo vše názornější. Někdy je implementace iterátoru uložena v konkrétní třídě formou vnitřní třídy.

Page 92: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

92

8.2 Vzor skladba (Composite) Pokud budeme rozšiřovat strukturu menu uvedenou v kapitole 8.1, můžeme se dostat do stavu, kdy místo položky menu (menuItem) zařadíme další menu. To je znázorněno na obr. 8.6, kde v Dinner menu je do jedné z jeho položek zařazeno další Desert menu. Dostáváme tak stromovou struktu, kde rozlišujeme uzly nebo složeniny (node nebo composite), které se skládají z dalších uzlů nebo listů a listy (leaf), které jsou konečné prvky, v našem případě položky menu.

Obr. 8.6 Obecná struktura menu Problém: Jak organizovat ve stromové struktuře objekty reprezentující strukturu celek-část. Chceme mít možnost jednotného zacházení jak s objekty typu celek, tak s objekty typu část. Kontext: Složenina (Composite) obsahuje části, nebo celky dalších objektů. Rekurzivní vazba ve struktuře celek-část. Řešení: Návrhový vzor skladba (Composite), který dovoluje klientovi zacházet s individuálními objekty a se složenými objekty stejně. Základní struktura je zobrazena na obr. 8.7. Component představuje buď abstraktní třídu nebo rozhraní.

Page 93: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

93

Obr. 8.7 Základní struktura vzoru Composite Vzor Skladba (Composite) umožňuje skládat objekty do stromových struktur k reprezentování hierarchie část-celek. Vzor Composite dovoluje klientům zacházet s individuálními objekty a složenými objekty (compositions) jednotným způsobem. Použitím struktury composite, můžeme aplikovat stejné operace jak na složené objekty, tak na individuální objekty – můžeme ignorovat rozdíly mezi složenými a individuálními objekty. Pokud se vrátíme k obr. 8.6 a budeme ho chtít realizovat, vyplynou z toho následující požadavky:

• Potřebujeme nějaký druh stromové struktury, která bude schopna obsahovat a udržovat menu, submenu a položky menu (menu items).

• Potřebujeme se ujistit, že udržíme způsob traverzování položkami alespoň tak, jak to bylo s iterátory.

• Potřebujeme traverzovat položky pružnějším způsobem. Např. potřebujeme traverzovat pouze Dinner submenu.

Obr. 8.8 Struktura vzoru Skladba

Page 94: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

94

Třída Client používá metod Component pro práci se složenými objekty ve stromové struktuře. Třída Leaf také dědí ostatní metody, na které neumí reagovat. Metody třídy/rozhraní Component podtřídy předeklarují. Metoda getChild(int) vrací celek na pozici danou celočíselným parametrem. Pokud navrhneme menu se vzorem Skladba jeho struktura v diagramu tříd UML bude následující:

Obr. 8.9 Diagram tříd pro strukturu menu Třída Waitress reprezentuje třídu Client z předchozího obrázku. MenuComponent je v tomto případě abstraktní třída. Programové řešení třídy MenuComponent je následující: public abstract class MenuComponent { // metody pro MenuComponents public void add(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public void remove(MenuComponent menuComponent) { throw new UnsupportedOperationException(); } public MenuComponent getChild(int i) { throw new UnsupportedOperationException(); } // metody pro MenuItems public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public double getPrice() {

Page 95: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

95

throw new UnsupportedOperationException(); } public boolean isVegetarian() { throw new UnsupportedOperationException(); } public void print() { throw new UnsupportedOperationException(); } }

Z deklarace kódu vidíme, že všechny metody vyhazují výjimku UnsupportedOperationException(). To je z toho důvodu, protože je tím zabezpečeno, že uvedené metody nemusí být zastíněny („overridden“) v podtřídách. Následuje třída MenuItem: public class MenuItem extends MenuComponent { String name; String description; boolean vegetarian; double price; public MenuItem(String name, String description, boolean vegetarian, double price) { this.name = name; this.description = description; this.vegetarian = vegetarian; this.price = price; } public String getName() { return name; } public String getDescription() { return description; } public double getPrice() { return price; } public boolean isVegetarian() { return vegetarian; } public void print() { System.out.print(" " + getName()); if (isVegetarian()) { System.out.print("(v)"); } System.out.println(", " + getPrice()); System.out.println(" -- " + getDescription()); } } Metoda print() se trochu odlišuje od předchozí implementace. Tiskne i informaci o tom, zda je jídlo vegetariálské. Nyní uvedeme programové řešení třídy Menu implementované jako složené (kompozitní) menu.

Page 96: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

96

public class Menu extends MenuComponent { ArrayList menuComponents = new ArrayList(); String name; String description; public Menu(String name, String description) { this.name = name; this.description = description; } public void add(MenuComponent menuComponent) { menuComponents.add(menuComponent); } public void remove(MenuComponent menuComponent) { menuComponents.remove(menuComponent); } public MenuComponent getChild(int i) { return (MenuComponent)menuComponents.get(i); } public String getName() { return name; } public String getDescription() { return description; } public void print() { System.out.print("\n" + getName()); System.out.println(", " + getDescription()); System.out.println("---------------------"); Iterator iterator = menuComponents.iterator(); while (iterator.hasNext()) { MenuComponent menuComponent = (MenuComponent)iterator.next(); menuComponent.print(); } } } Hned v úvodu nám přibyl datový atribut typu ArrayList, který umožňuje deklarovat další kolekci. Metodu print() jsme také museli opravit, aby tiskla všechny části, ze kterých se celek na daném místě může skládat. public class Waitress { MenuComponent allMenus; public Waitress(MenuComponent allMenus) { this.allMenus = allMenus; }

Page 97: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

97

public void printMenu() { allMenus.print(); } }

Jednoduchý kód, kterým můžeme všechny položky vytisknout, představuje metoda printMenu() ve třídě Waitress. Ke spuštění ověření dané aplikace bychom však potřebovali ještě kód, který by nám postupně vytvořil strukturu menu a naplnil je daty.

Vzor Composite obsahuje dva silné koncepty, které jsou ve vzájemné vazbě. Prvním konceptem je, že skupina může obsahovat buď individuální objekty (položky), nebo další skupiny. Ve vazbě s tímto konceptem je koncept s myšlenkou, že jak skupiny, tak individuální položky sdílejí společné rozhraní. Tyto myšlenky spolu s objektovým modelováním mohou vytvořit abstraktní třídu, nebo rozhraní, které definuje společné chování jak pro skupiny, tak pro individuální objekty. Modelování vzoru Composite často vede k rekurzivním definicím metod na uzlech složeného objektu. Je třeba se vyvarovat nekonečných cyklů a zabezpečit, aby navržená struktura byla vždycky stromem.

Page 98: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

9 Vzor Stav (State)

V této kapitole se dozvíte:

• jak distribuovat logiku, která je závislá na stavech (atributech objektu) na tř

• jak se s využitím návrhového vzoru zavedené stavy.

Po jejím prostudování budete schopni:

• navrhnout a aplikovat návrhového vzoru.

Klí čová slova této kapitoly:

stavy objektu, modifikace stav

Kontext: Stav objektu je dán kombinací aktuálních hodnot jeho atributů. Hodnoty těchto atributzaslání zprávy set objektu, se patributu objektu. Tím se změvlastní stavy, při vykonání jejich metod. Problém: V některých případech však jednoduchá implementace tohoto principu pomocí if nebo switch příkazkonstrukcím a jejich obtížné údržb Představme si, že v daném objektu existuje datovkterý může nabývat tří hodnot 1, 2, 3.objekt rozhoduje v metodě a2, a3) např. takto: public operaceA() { . . . switch (atrState) { 1 : ob.a1( ); 2 : ob.a2( ); 3 : ob.a3( ); } public operaceB() { . . . switch (atrState) { 1 : ob.b1( ); 2 : ob.b2( ); 3 : ob.b3( ); }

98

jak distribuovat logiku, která je závislá na stavech objektu (atributech objektu) na třídy, které reprezentují stavy objektu,

využitím návrhového vzoru Stav dájí snadno modifikovat

Po jejím prostudování budete schopni:

navrhnout a aplikovat řešení využívající znalostí tohoto

ová slova této kapitoly:

stavy objektu, modifikace stavů objektu

kombinací aktuálních hodnot jeho datových chto atributů měníme pomocí metod set(). Při

objektu, se přiřadí nějaká hodnota příslušnému atributu objektu. Tím se změnil jeho stav. Objekty obyčejně mění své

i vykonání jejich metod.

ípadech však jednoduchá implementace tohoto principu příkazů může vést k velmi nepřehledným

konstrukcím a jejich obtížné údržbě.

daném objektu existuje datový atribut atrState, í hodnot 1, 2, 3. Na základě tohoto stavu se pak

ě OperaceA() pro volání jiných metod (a1,

Page 99: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

99

Celé programové řešení uvedeného problému bez použití návrhového vzoru Stav je následující: public class PuvodniTrida { private int state = 1; State1 state1 = new State1(); State2 state2 = new State2(); State3 state3 = new State3(); public PuvodniTrida() { } public void increment() { state = (state + 1) % 4; if(state==0) state++; } public void run() { switch(state) { case 1: {state1.operaceA(); state1.operaceB(); break; } case 2: {state2.operaceA(); state2.operaceB(); break; } case 3: { state3.operaceA(); state3.operaceB(); break; } } } } Metoda increment() zvyšuje hodnotu proměnné state o jedna a zároveň ji udržuje v rozsahu 1 až 3. K tomu je využita operace zbytku po celočíselném dělení. Následuje kód hlavní metody: public class PuvodniTridaTest { public static void main(String[] args) { PuvodniTrida puvodniTrida = new PuvodniTrida(); puvodniTrida.run(); puvodniTrida.increment(); puvodniTrida.run(); puvodniTrida.increment(); puvodniTrida.run(); puvodniTrida.increment(); puvodniTrida.run(); } } Řešení: Větvení, tak jak je uvedeno ve schematickém příkladu se provádět nebude. Rozdělení kódu se provede tak, že se k sobě dají operace pro stejný stav (mají samozřejmě různou implementaci) a to: a1 a b1 a a2 a b2 atd. Toto rozdělení se provede pomocí nových tříd. Zavede se nové rozhraní, které můžeme nazvat State. Toto rozhraní bude deklarovat

Page 100: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

100

polymorfní operace (operace mající stejný název, ale různou implementaci) operaceA( ) i operaceB( ). V dalším kroku vytvoříme tolik tříd implementujících rozhraní State, kolik je stavů. Pokud máme tři stavy, budou to tři třídy, které budou mít různou implementaci metod (operaceA( ) operaceB( )). Operace operA() v PuvodniTrida vyvolá state.operaceA(). state je datový atribut v PuvodniTrida a nabývá hodnot objektů svých podtříd, tedy state1, state2 nebo state3. Ve třídách State1, State2 a State3 jsou metody operaceA() a operaceB() implementovány různě.

Obr. 9.1 Vzor stav (State) Kód programu, který by realizoval uvedený diagram tříd pro návrhový vzor Stav je následující: public interface State { public void operaceA(); public void operaceB(); } public class State1 implements State { public void operaceA() { System.out.println(getClass().getName() + "operaceA()"); } public void operaceB() { System.out.println(getClass().getName() + "operaceB()"); } }

Zde uvádíme pouze kód třídy State1, protože ostatní dvě třídy State2 a State3 jsou téměř totožné.

Page 101: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

101

public class TridaState { private State state; public TridaState(State state) { this.state = state; } public void changeState(State state) { this.state = state; } public void operaceA() { state.operaceA(); } public void operaceB() { state.operaceB(); } }

Třída TridaState realizuje vlastní návrhový vzor. Datový atribut state udržuje vazbu na rozhraní a zároveň nabývá konkrétních hodnot instancí tříd, které rozhraní implementovaly, tedy instancí state1, state2 nebo state3. Konstruktor třídy nastavuje proměnnou state a metoda changeState() mění proměnnou state. Další kód ukazuje použití tohoto vzoru: public class StateTest { public static void main(String[] args) { TridaState puvodniTrida = new TridaState(new State1()); puvodniTrida.operaceA(); puvodniTrida.operaceB(); puvodniTrida.changeState(new State2()); puvodniTrida.operaceA(); puvodniTrida.operaceB(); puvodniTrida.changeState(new State3()); puvodniTrida.operaceA(); puvodniTrida.operaceB(); } }

Uvedený příklad odpovídá diagramu tříd, který je uveden na obr. 9.2. Je to ale více méně ilustrativní příklad. Pro lepší pochopení uvedeného návrhového vzoru si proto ještě uvedeme konkrétní příklad automatických dveří někde v obchodním domě. Tyto dveře pracují podle následujícího diagramu stavů a uvedených přechodů mezi danými stavy.

Page 102: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

102

Obr. 9.2 Diagram stavů a přechodů automatických dveří. Z diagramu je zřejmé, že dveře mají pět stavů. Mezi jednotlivými stavy existují přechody, které jsou vyvolány akcí člověka. Akce touch znamená dotknutí se dveří. Akce complete znamená dokončení operace. Akce timeout znamená, že po uplynutí dané doby nastane uvedený přechod mezi stavy. Další obrázek ukazuje zobrazení uvedené situace v diagramu tříd UML. Třída Door má deklarovaný atribut state typu DoorState, kterým si udržuje stav svých dveří a dále ho využívá ve vlastních metodách jako objekt příjemci, např. state.complete(). Metoda setState() mění stav proměnné state a metoda status() je pouze pro vypsání textových informací o aktuálním stavu automatických dveří.

Page 103: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

103

DoorState()

complete()

status()

timeout()

touch()

door: Door

DoorState

complete()

setState()

status()

timeout()

touch()

state: DoorState

Door

touch()

complete()

DoorOpening

touch()

DoorClosed

touch()

complete()

DoorClosing

touch()

timeout()

DoorOpen

touch()

DoorStayOpen

Obr. 9.3 Znázornění aplikace automatických dveří diagramem tříd UML. DoorState není rozhraní, tak jako jsme to měli uvedené v předchozím příkladě, ale abstraktní třída. To má především dva důvody. Prvním je deklarace datového atributu door třídy Door a druhým je možnost deklarace konstruktoru, který je volán podtřídami uvedené abstraktní třídy. Nejdříve si uvedeme kód třídy Door. public class Door { // vnitřní třídy, // přístup přes např. door.OPEN, nebo door.STAYOPEN public final DoorState CLOSED = new DoorClosed(this); public final DoorState CLOSING = new DoorClosing(this); public final DoorState OPEN = new DoorOpen(this); public final DoorState OPENING = new DoorOpening(this); public final DoorState STAYOPEN = new DoorStayOpen(this); private DoorState state = CLOSED; public void touch() { state.touch(); } public void complete() { state.complete(); } public void timeout() {

Page 104: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

104

state.timeout(); } public String status() { return state.status(); } protected void setState(DoorState state) { this.state = state; } public void print() { System.out.println("Dvere stav: " + this.status()); } } Tato třída v úvodu deklaruje 5 neměnných instancí tříd jednotlivých stavů. Všiměte si parametru this, který představuje odkaz na třídu Door (tedy dveře). Dále si uvedeme abstraktní třídu DoorState. public abstract class DoorState { private Door door; public DoorState(Door door) { this.door = door; } public Door getDoor() { return door; } public abstract void touch(); public void complete() { } public void timeout() { } public String status() { String s = getClass().getName(); return s.substring(s.lastIndexOf('.') + 1); } } Abstraktní třída má deklarovaný konstruktor a konkrétní metodu status(), která vypíše zpracovávanou třídu stavu bez jména javovského balíčku. public class DoorClosed extends DoorState { public DoorClosed(Door door) { super(door); } public void touch() { getDoor().setState(getDoor().OPENING); } }

Page 105: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

105

Třída DoorClosed má kromě konstruktoru ještě deklarovanou metodu touch(), ktrerá mění proměnnou state na door.OPENING. Instance OPENING je deklarovaná v rámci třídy Door a proto musíme použít uvedenou konstrukci. Podobné je to i s ostatními třídami stavů. Uvedeme si proto jen třídu DoorOpen. public class DoorOpen extends DoorState { public DoorOpen(Door door) { super(door); } public void touch() { getDoor().setState(getDoor().STAYOPEN); } public void timeout() { getDoor().setState(getDoor().CLOSING); } } Aplikace pro otestování funkčnosti je následující: public class DoorTest { public static void main(String[] args) { Door door = new Door(); door.print(); door.touch(); door.print(); door.touch(); door.print(); door.complete(); door.print(); door.touch(); door.complete(); door.print(); door.timeout(); door.print(); } } Jak vidíme z kódu, pracujeme pouze s instancí třídy Door, které posíláme metody deklarované v této třídě. Návrhový vzor State využívá ker své činnosti polymorfismu. Mezi vlastní třídou a jednotlivými stavy, reprezentovanými také třídami zavedeme buď rozhraní, nebo abstraktní třídu. Vlastní třídy dílčích stavů jsou pak buď podtřídami abstraktní třídy, nebo implementují metody rozhraní. Výhodou tohoto vzoru je snadná manipulace se stavy ve smyslu jejich modifikace, tedy přidávání dalších stavů nebo jejich rušení (odebírání). Úpravy jsou přehledné a snadno proveditelné.

Page 106: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

10 Vzor Proxy

V této kapitole se dozvíte:

• jak využívat objekt

reálným objektem,

• jaké další možné operace tato vazba umož

• jak se dá využívat reflexe v

Po jejím prostudování budete schopni:

• lépe porozumět možnostem používání vzoru proxy,

• porozumět možnostem, které nabízí dynamické proxy.

Klí čová slova této kapitoly:

vzdálené proxy, dynamické proxy,

Tento vzor zprostředkuje to, že klient nekomunikuje pkomponentou, ale s jejím zástupcem (reprezentantem). Vytvotakového zástupce může sloužit mnoha úefektivnosti, snadnějšímu přpřístupem. Klasickým příkladem je použití tohoto vzoru vsíti. Úkolem je vytvořit technologii pjiném stroji. Komponenta klientkomponenta (server) na jiném stroji. Vyžaduje se, aby klient pracoval s touto komponentou stejně, jako by byla u nstejným způsobem její operace). Jako řešení se použije komponenta rozhraní, jako původní komponenta. Komponenta technologii propojení mezi stroji.

Kontext

Klient potřebuje přístup ke službám jiné komponenty. Ptechnicky možný, ale nemusí být nejlepším

106

jak využívat objekt „zástupce“ (proxy) ve styku s původním

jaké další možné operace tato vazba umožňuje,

jak se dá využívat reflexe v tomto návrhovém vzoru.

Po jejím prostudování budete schopni:

t možnostem používání vzoru proxy,

ostem, které nabízí dynamické proxy.

ová slova této kapitoly:

vzdálené proxy, dynamické proxy, rozhraní InvocationHandler

edkuje to, že klient nekomunikuje přímo s danou komponentou, ale s jejím zástupcem (reprezentantem). Vytvořením

že sloužit mnoha účelům včetně rozšíření jšímu přístupu a ochraně před neautorizovaným

íkladem je použití tohoto vzoru v remote technologiích na it technologii přístupu ke vzdálenému objektu na

klient se nachází na jednom stroji a vzdálená ) na jiném stroji. Vyžaduje se, aby klient pracoval

ě, jako by byla u něj na stejném stroji (volá sobem její operace).

ešení se použije komponenta proxy, která poskytuje na vně stejné vodní komponenta. Komponenta proxy zabezpečuje

technologii propojení mezi stroji.

ístup ke službám jiné komponenty. Přímý přístup je technicky možný, ale nemusí být nejlepším řešením.

Page 107: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

107

Problém

• Přístup ke komponentě by měl být efektivní (za běhu programu), finančně méně náročný a bezpečný jak pro klienta, tak pro komponentu.

• Přístup by měl být pro klienta transparentní a jednoduchý.

Řešení

Klient bude komunikovat se zástupcem (nazývaným proxy) místo s danou komponentou. Proxy kromě poskytnutí stejného rozhraní, poskytuje ještě další operace tzv. pre a post processing jako např. kontrolu přístupu, vytváření pouze read-only kopií původní komponenty atd.

Struktura

Klient, který potřebuje některé funkce originální komponenty svoji žádost směřuje na proxy (nepřímou cestou). Klient nemění své chování ani syntaxi požadavků. Proxy poskytuje stejné rozhraní jako originální komponenta. Z důvodů realizovatelnosti, proxy udržuje referenci na původní (originální) komponentu, kterou zastupuje. Obyčejně mezi proxy a originální komponentou existuje asociace jedna k jedné.

Obr. 10.1 Základní struktura vzoru proxy

+service_1()+service_2()

AbstractOriginal

+task()

Client

+service_1()+service_2()

Proxy

+service_1()+service_2()

Original

Page 108: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

108

Obr. 10.2 Vzor proxy s využitím sekvenčního diagramu UML.

Implementace

1. Identifikovat všechny požadavky na práci s přístupem k původní

komponentě. 2. Je-li to možné vytvořit abstraktní třídu, která specifikuje společnou část rozhraní jak pro komponentu proxy, tak pro původní komponentu.

3. Implementovat funkce proxy. 4. Oprostit původní komponentu od klientů a služeb, které se

přesunou na komponentu proxy. 5. Asociovat proxy komponentu a původní komponentu tím, že

komponenta proxy bude mít referenci na původní komponentu. 6. Odstranit všechny přímé vazby mezi původní komponentou a

komponentami jejich klientů.

Varianty

• Remote proxy. Klienti vzdálených komponent by měly být ochráněni od síťových adres a mezi procesových komunikačních protokolů.

• Protection proxy. Komponenty musí být chráněny od neautorizovaného přístupu.

• Cache proxy. Vícenásobní lokální klienti mohou sdílet výsledky ze vzdálených komponent.

• Synchronization proxy. Vícenásobné současné přístupy ke komponentě musí být synchronizovány.

• Counting proxy. Používá se pro vedení statistik přístupu, nebo pro odstranění nepoužívaných komponent.

Client Proxy Original

task

service

pre-processing

service

post-processing

Page 109: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

109

• Virtual proxy. Je známé též pod označením lazy construction. Používá se zejména tehdy, je-li komponenta uložena na pevném disku. Zpracování, nebo nahrávání celé komponenty (z pevného disku) může být nákladné, zatímco částečné informace o komponentě mohou být dostatečné.

• Firewall proxy. Lokální klienti jsou chráněni od vnějšího světa.

• Dynamické proxy. Umožní kontrolu operací zaslaných reálnému objektu a tím jejich eventuální provedení nebo neprovedení.

Page 110: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

110

10.1 Vzdálené proxy (Remote Proxy)

Tato varianta nastává, když objekt, jehož metodu chcete vyvolat běží na jiném počítači (na jiném JVM) a není možné jeho metodu vyvolat přímo (běží obecně v jiném adresovém prostoru). Vzdálené proxy funguje jako lokální reprezentant vzdáleného objektu, který chceme volat. Lokální reprerzentant je objekt, jehož metody můžeme lokálně volat a který naše požadavky přesouvá na objekt běžící v jiném adresovém prostoru – vzdálený objekt. Komunikace se vzdáleným objektem by měla fungovat, jako kdybychom pracovali na stejném počítači. Obr. 9.3 to názorně vystihuje.

Obr. 10.3 Vzdálené proxy

Objekt Proxy (na straně klienta) – obr. 10.3 vlevo, bývá označován také jako stub / proxy, nebo jako Client helper (pomocník klienta). Podobný objekt na straně serveru (na obr. 9.1 není naznačen), bývá označován jako skeleton, nebo Service helper (pomocník serveru).

Obr. 10.4 Komunikace mezi klientem a vzdáleným JVM

Page 111: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

111

Client helper zabalí volání (argumenty, jméno metody atd.) a odešle je přes síť service helperu.

Service helper rozbalí zaslané informace, nalezne, kterou metodu volat na kterém objektu a vyvolá reálnou metodu reálného objektu.

Obr. 10.5 Zpětná komunikace mezi vzdáleným JVM (serverem) a klientem (lokálním JVM)

Vyvolaná metoda na ServiceObject vrací nějaké výsledky ServiceHelperu. ServiceHelper zabalí informace získané z volání a vrací je zpět přes síť ClientHelperu. Client helper rozbalí zaslané informace a vrátí je ClientObjectu.

Implementace v Javě

Java realizuje tento typ komunikace prostřednictvím RMI (Remote Method Invovation).

Aplikace distribuovaných objektů potřebují:

• Lokalizovat (najít) vzdálený objekt.

– Aplikace může registrovat svůj vzdálený objekt pomocí jednoduchého jmenného RMI prostředku rmiregistry.

– Aplikace může předat nebo vrátit odkaz na vzdálený objekt.

• Komunikace se vzdáleným objektem.

• Nahrání bytekódu objektů, které jsou předány jako parametry, nebo návratové hodnoty.

Page 112: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

112

Obr. 10.6 Schéma distribuované komunikace

Server zavolá registry, aby asocioval své jméno s vzdáleným objektem.

Klient vyhledává vzdálený objekt podle jeho jména v registry serveru a potom vyvolá na serveru příslušnou metodu vzdáleného objektu.

RMI systém používá existující Web server k na natažení bytekódu, tříd napsaných v Javě, ze serveru na klienta a z klienta na server, je-li to nutné.

RMI může natáhnout bytecode třídy s použitím URL protokolů.

Postup vytvoření aplikace

1. Definování vzdáleného rozhraní.

2. Implementace serveru.

3. Implementace klienta.

4. Kompilace zdrojových kódů.

5. Start –

– Java RMI registry,

– server,

– klient.

Tento postup aplikujeme na jednoduchý příklad. Vzdálené rozhraní definuje jen jednu metodu. Dále si všimněte balíčků, které je třeba importovat. První balíček zabezpečuje vzdálenou komunikaci a druhý balíček je využíván při přerušení této komunikace. Každá „vzdálená“ metoda musí vyhazovat RemoteException.

Definování vzdáleného rozhraní

import java.rmi.Remote; import java.rmi.RemoteException; public interface Hello extends Remote { String sayHello() throws RemoteException; }

Page 113: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

113

Implementace serveru

Metoda main serveru potřebuje vytvořit vzdálený objekt, který poskytuje služby. Vzdálený objekt musí být navíc exportovaný do běhového Java RMI runtime, tak aby mohl obdržet přicházející vzdálená volání metod. To provede příkaz se statickou metodou exportObject().

import java.rmi.registry.Registry; import java.rmi.registry.LocateRegistry; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class Server implements Hello { // konstruktor public Server() {} public String sayHello() { return "Hello, world!"; } // hlavní metoda public static void main(String args[]) { try { Server obj = new Server(); //instance serveru Hello skeleton = (Hello) UnicastRemoteObject.exportObject(obj, 0); // registrace vzdáleného objektu skeleton v registry Registry registry = LocateRegistry.getRegistry(); registry.bind("Hello", skeleton); // zpráva, server je připraven System.err.println("Server ready"); } catch (Exception e) { System.err.println("Server exception: " + e.toString()); e.printStackTrace(); } } }

Třídní metoda UnicastRemoteObject.exportObject() exportuje dodaný vzdálená objekt, aby mohl obdržet příchozí vzdálené volání metody na anonymním portu TCP a vrátit objekt skeleton pro předání vzdáleného objektu klientovi.

Výsledkem volání uvedené metody je, že běhové prostředí může začít poslouchat na novém soketu serveru, aby přijalo příchozí vzdálená volání vzdáleného objektu.

Navrácený skeleton implementuje stejné rozhraní jako třída vzdáleného objektu a obsahuje jméno a port hostitelského počítače, ze kterého může být vzdálený objekt kontaktován.

Page 114: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

114

Implementace klienta

import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { private Client() {} public static void main(String[] args) { String host = (args.length < 1) ? null : args[0]; try { Registry registry = LocateRegistry.getRegistry(host); Hello stub = (Hello) registry.lookup("Hello"); String response = stub.sayHello(); System.out.println("Odpoved: " + response); } catch (Exception e) { System.err.println("Client exception: " + e.toString()); e.printStackTrace(); } } }

Není-li deklarovaný host, je použit localhost. stub – představuje proxy a je to odkaz na vzdálený objekt.

Při vlastním spuštění aplikace musíme pamatovat na to, že potřebujeme dva odlišné adresové prostory (namespace, dvě různá JVM). V jednoduchém případě můžeme použít jako server localhost. Vytvoříme si dva příkazové řádky a první dva příkazy provedeme postupně v jednom příkazovém řádku a třetí příkaz pak ve druhém příkazovém řádku.

1. Start Java RMI registry

start rmiregistry

2. Start serveru

java Server

// pokud jsme v baličcích example.hello pak

java example.hello.Server

3. Spuštění klienta

java Client // nebo java example.hello.Client

Pokud bude vše v pořádku, nejdříve se nám vypíše zpráva, že rmiregistry bylo spuštěno. Pak naše zpráva „Server ready“.

V dalším příkazovém řádku se pak vypíše „Odpoved: Hello, World“.

Page 115: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

115

10.2 Dynamické proxy

Dynamické proxy je další varianta použití návrhového vzoru Proxy. Nabízí obalit daný objekt proxy objektem. Vnější objekt – proxy, bude přijímat (odposlouchávat) všechna volání určena pro vnitřní „zabalený“ objekt.

Problém: Jak za běhu programu zjistit, zda reálnému objektu nehrozí skutečné nebezpečí nebo nepřípustná operace.

Kontext: Prověřování metod za běhu programu využívající vzor proxy.

Řešení: Doplnit objekt Proxy o objekt InvocationHandler, který umí pracovat s reflexí.

Proxy objekt standardně předává všechna volání vnitřnímu objektu, ale je možné přidat kód, který se vykoná před, nebo po vyvolání metody vnitřního objektu. Není možné samozřejmě „zabalit“ libovolný objekt. I přesto umožňují dymanická proxy úplnou kontrolu nad „zabaleným“ objektem.

Dynamické proxy pracuje s rozhraními, které třída objektu implementuje. Volání, která může proxy zachytit jsou volání jednoho z těchto definovaných rozhraní.

request()

«interface»

Subject

invoke()

«interface»

InvocationHandler

request()

RealSubject

request()

Proxy

invoke()

InvocationHandler

Obr. 10.7 Struktura dynamického proxy

Proxy, jak vidíme na obrázku 10.7, se nyní skládá ze dvou tříd, a to z původní třídy Proxy, která zprostředkovává vlastní styk s klientskou třídou a třída InvocationHandler. Třída InvocationHandler dostává všechna volání metod, které směřují na proxy. Navíc implementuje

Page 116: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

116

„reflexní“ metodu invoke(), která umožňuje pomocí reflexe (viz kapitola o reflexi) vyvolat metodu request() třídy RealSubject. Řídí tak přístup metod k RealSubjectu. Třída Proxy implementuje celé rozhraní Subject.

Konkrétní p říklad Využití dynamického proxy si ukážeme na konkrétním příkladu. Budeme sledovat osoby, které budou uloženy v hashovací tabulce (databázi) a které budou mít dva druhy datových atributů. První druh datových atributů budou „soukromé“ datové atributy jako jsou jméno, pohlaví a záliby, které bude moci konkrétní osoba jak měnit, tak zpřístupnit (povoleny metody get/set). Druhým typem datového atrubutu bude hodnocení (HotOrNotRating) v rozsahu od 0 do 10 (0 nejmenší hodnocení, 10 nejvyšší hodnocení). Chceme, aby tento druhý druh datového atributu měla daná osoba pouze zpřístupněn (metoda get) a nemohla ho měnit. Naproti tomu ostatní osoby v „databázi“ moho měnit a zpřístupnit si tento datový atribut.

Aplikace tedy bude muset rozlišovat, zda je osoba stejná osoba s hodnocenou osobou nebo ne.

Nejdříve se podíváme na kód rozhraní osoby a pak na vlastní třídu Osoba.

public interface PersonBean { String getName(); String getGender(); String getInterests(); int getHotOrNotRating(); void setName(String name); void setGender(String gender); void setInterests(String interests); void setHotOrNotRating(int rating); }

Následuje třída Osoba, která implementuje uvedené rozhraní.

public class PersonBeanImpl implements PersonBean { String name; String gender; String interests; int rating; int ratingCount = 0; public String getName() { return name; } public void setName(String name) { this.name = name;

Page 117: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

117

} // další přístupové a modifikační metody . . . public int getHotOrNotRating() { if (ratingCount == 0) return 0; return (rating/ratingCount); } public void setHotOrNotRating(int rating) { this.rating += rating; ratingCount++; } }

Z kódu vidíme, že metoda setHotOrNotRating() postupně načítá jak hodnocení, tak i počet osob, které hodnocení provedli. Metoda getHotOrNotRating() pak z těchto hodnot spočítá průměr.

Dále si uvedeme kódy dvou tříd, které budou rozlišovat, o jakou osobu se jedná, tedy zda osoba, která chce např. hodnotit, není stejná s hodnocenou osobou. První třída OwnerInvocationHandler je třída, kdy hodnocená osoba je shodná s hodnotící osobou. Proto také nastavuje odpovídající pravidla.

public class OwnerInvocationHandler implements InvocationHandler { PersonBean person; public OwnerInvocationHandler(PersonBean person) { this.person = person; } public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if (method.getName().startsWith("get")) { return method.invoke(person, args); } else if (method.getName().equals("setHotOrNotRating")) { throw new IllegalAccessException(); } else if (method.getName().startsWith("set")) { return method.invoke(person, args); } } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } }

Pokud se jedná o přístupové metody (začínají na „get“) není důvod něčemu bránit. Vyvolání metody je provedeno přes metodu invoke().

Page 118: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

118

Pokud ale se jedná o metodu „setHodOrNotRating()“ je vyhozena výjimka, která informuje o nedovolené akci. Jedná-li se o jiné metody, které začínají na „set“ je metoda realizováva reflexní metodou invoke().

public class NonOwnerInvocationHandler implements InvocationHandler { PersonBean person; public NonOwnerInvocationHandler(PersonBean person) { this.person = person; } public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if (method.getName().startsWith("get")) { return method.invoke(person, args); } else if (method.getName().equals("setHotOrNotRating")) { return method.invoke(person, args); } else if (method.getName().startsWith("set")) { throw new IllegalAccessException(); } } catch (InvocationTargetException e) { e.printStackTrace(); } return null; } }

Třída NonOwnerInvocationHandler řeší problém, kdy hodnocená a hodnotící osoba jsou jiné a tehdy hodnotící osoba nesmí měnit např. jméno nebo pohlaví.

Dále uvedeme kód „databáze“, ve které budou uvedeny testované osoby.

public class Database { private Hashtable<String, PersonBean> datingDB; // konstruktor + úvodní naplnění databáze public Database() { datingDB = new Hashtable<String, PersonBean>(); initializeDatabase(); } public Hashtable<String, PersonBean> getDatabase() { return datingDB; } // naplnění databáze daty public void initializeDatabase() { PersonBean joe = new PersonBeanImpl();

Page 119: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

119

joe.setName("Joe Javabean"); joe.setInterests("cars, computers, music"); joe.setHotOrNotRating(7); datingDB.put(joe.getName(), joe); PersonBean kelly = new PersonBeanImpl(); kelly.setName("Kelly Klosure"); kelly.setInterests("ebay, movies, music"); kelly.setHotOrNotRating(6); datingDB.put(kelly.getName(), kelly); } public PersonBean getPersonFromDatabase(String name) { return (PersonBean)datingDB.get(name); } public PersonBean getOwnerProxy(PersonBean person) { return (PersonBean) Proxy.newProxyInstance( person.getClass().getClassLoader(), person.getClass().getInterfaces(), new OwnerInvocationHandler(person)); } public PersonBean getNonOwnerProxy(PersonBean person) { return (PersonBean) Proxy.newProxyInstance( person.getClass().getClassLoader(), person.getClass().getInterfaces(), new NonOwnerInvocationHandler(person)); } }

Třída Databaze má datový atribut datingDB deklarovaný jako HashTable. V konstruktoru se vyvolá metoda initializeDatabaze(), která naplní do hashovací tabulky dva záznamy. Klíčem je řetězec (jméno) a hodnotou je celý záznam (PearsonBeanImpl). Metody getOwnerProxy(), stejně jako getNonOwnerProxy() vyžadují jako parametr osobu.

public class ProxyTest { public static void main(String[] args) { Database database = new Database(); PersonBean joe = database.getPersonFromDatabase("Joe Javabean"); PersonBean ownerProxy = database.getOwnerProxy(joe); System.out.println("Name is " + ownerProxy.getName()); ownerProxy.setInterests("bowling, Go"); System.out.println("Interests set from owner proxy"); try { ownerProxy.setHotOrNotRating(10);

Page 120: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

120

} catch (Exception e) { System.out.println("Can't set rating from owner proxy"); } System.out.println("Rating is " + ownerProxy.getHotOrNotRating()); PersonBean nonOwnerProxy = database.getNonOwnerProxy(joe); System.out.println("Name is " + nonOwnerProxy.getName()); try { nonOwnerProxy.setInterests("bowling, Go"); } catch (Exception e) { System.out.println("Can't set interests from non owner proxy"); } nonOwnerProxy.setHotOrNotRating(3); System.out.println("Rating set from non owner proxy"); System.out.println("Rating is " + nonOwnerProxy.getHotOrNotRating()); } }

Třída ProxyTest pak uvádí využití dynamického proxy. Z kódu vidíme, že se k osobám dostáváme prostřednictvím objektu ownerProxy nebo nonOwnerProxy. Ty pak vlastně ovlivňují přístup k reálnému subjektu, v našem případě instanci PersonBeanImpl.

Proxy je dynamické, protože jeho třída je vytvořena za běhu programu. Proxy je vytvořeno na základě požadavku (množina rozhraní). Naproti tomu InvocationHandler není proxy, je to jen takový dispečer, kterého proxy využívá pro zpracování volaných metod. Implementace vzoru proxy vytvoří zástupný objekt, který řídí přístup k cílovému objektu. Proxy objekt může izolovat klienty od změn ve stavech požadovaného objektu, jako např. při stahování obrázků vyžadujících delší trvání. Problém proxy způsobuje vyžadovaná pevná vazba mezi zástupným objektem a původním objektem. Dynamické proxy může nabízet využití při zachycování volání cílovému objektu.

Použití

Vzor Proxy má bohaté použití viz různé varianty tohoto vzoru. My jsme se konkrétně zabývali dvěmi variantami a to remote proxy a dynamickým proxy. Další využití je např. OMG-CORBA používá vzor proxy pro ochranu klientů před konkrétní implementací jejich serverů a dále při odesílání požadavků konkrétním vzdáleným komponentám.

Page 121: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

121

World Wide Web Proxy používá firewall proxy.

Výhody

Zvýšení efektivnosti a nižší cena. Přímé rozpojení klienta s původní (originální) komponentou. Slabá místa Menší efektivnost z důvodu nepřímosti spojení. Návrhový vzor proxy se používá jako zástupce pro komponenty, které potřebují komunikovat s originálem. Má řadu výhod a různých variant použití.

Page 122: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

122

11 Vzory architektury

V této kapitole se dozvíte:

• co jsou dva základní vzory softwarové architektury,

• jaké mají praktické implementace.

Po jejím prostudování budete schopni:

• navrhnout a využívat architekturu vrstev,

• navrhnout a využívat architekturu rour a filtrů.

11.1 Vzor vrstvy (Layers)

Vzor Vrstvy (Layers) pomáhá strukturovat aplikace, které mohou být dekomponovány na skupiny podúloh, ve kterých každá skupina podúloh je v dané úrovni abstrakce. Nejznámějším příkladem architektury vrstev jsou síťové protokoly. Protokol se skládá z množiny pravidel a konvencí, které popisují, jak počítačové programy komunikují přes hranice počítačů. Je definován formát, obsah a význam zpráv. Protokol specifikuje dohody v množství abstraktních úrovní, začínající od přenosu bitů až po nejvyšší úroveň aplikační logiky. Každá vrstva pracuje se specifickým aspektem komunikace a používá služby bezprostředně nižší vrstvy. Mezinárodní organizace ISO definovala následující architektonický model - OSI sedmi vrstevný model. Nejvíce používaný komunikační protokol, TCP/IP, se plně neshoduje s OSI modelem a skládá se pouze ze čtyř vrstev viz obr. 11.1.

Page 123: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

123

Obr. 11.1 Čtyřvrstvý komunikační protokol TCP/IP Přístup po vrstvách je považován za lepší než implementace protokolu jako monolitického bloku, protože implementace koncepčně odlišných problémů odděleně přináší několik výhod např. podpora pro týmovou práci, podpora inkrementálního programování. Použití částečně nezávislých částí také umožňuje pozdější snadnější změnu dílčích částí. Ačkoli OSI je důležitý referenční model, převažujícím síťovým protokolem je TCP/IP. TCP/IP používáme k tomu, abychom ukázali jiný důležitý důvod pro vrstvení; znovupoužití jednotlivých vrstev v různém kontextu. Například TCP může být použit jako různorodé distribuované aplikace jako telnet nebo ftp .

Kontext

Rozsáhlý systém vyžadující dekompozici.

Problém

Představme si, že navrhujeme systém, jehož dominantní charakteristikou je kombinace nízko a vysoko úrovňových problémů, kde vysoce úrovňové operace závisí na nízko úrovňových. Typickým vzorem komunikačního toku požadavků je, že se pohybují z vysokoúrovňových k nízkoúrovňovým. Odpovědi na tyto dotazy mají směr opačný. Takovéto systémy si vyžadují horizontální strukturování, které je ortogonální k jejich vertikálnímu dělením. Toto je případ, kde několik operací je na stejné úrovni abstrakce, ale jsou z velké části nezávislé jedna na druhé. Systémová specifikace, kterou dostanete, popisuje do jistého rozsahu vysokoúrovňové úkoly a specifikuje cílovou platformu. Je také žádaná přenositelnost na jiné platformy. Mapování vysoko úrovňových úloh na

FTP FTP

TCP

IP

Ethernet

TCP

IP

Ethernet

FTP protokol

TCP protokol

IP protokol

Ethernet protokol

Fyzické propojení

Page 124: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

124

danou platformu není přímočaré, protože většinou jsou příliš komplexní na to, aby byly implementovány přímo a využívaly služeb dané platformy. V takovém případě potřebujeme dát do rovnováhy následující síly:

• pozdější změny kódu by neměly ovlivňovat celý systém, měly by být svázány s jednou vrstvou,

• stabilní interface,

• části systému by měly být zaměnitelné, komponenty by měly mít možnost nahradit alternativní implementace bez účinku na zbytek systému,

• neexistuje žádná standardní granularita,

• složité komponenty vyžadují další dekompozici.

Řešení

Z nejvyšší úrovně je řešení velmi jednoduché. Strukturujte váš systém do vhodného počtu vrstev a umístěte je na vrchol každé předcházející. Začněte nejnižší vrstvou abstrakce. Postupujte směrem nahoru od J-1 vrstvy k vrstvě J, až dosáhnete vrcholu. Většina služeb, které vrstva J poskytuje, jsou složené služby poskytované vrstvou J-1. Struktura Hlavní zásadou struktury tohoto vzoru je, že služby Vrstvy J jsou pouze použity Vrstvou J+1. Neexistuje žádná další závislost mezi vrstvami. Tato struktura může být srovnatelná se zásobníkem. Každá konkrétní vrstva chrání nižší vrstvy od přímého přístupu některé z vyšších vrstev.

Page 125: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

125

Obr. 11.2 Základní struktura návrhového vzoru Scénáře použití: 1. Klient zadává dotaz nejvyšší vrstvě N. Protože tato vrstva nemůže

požadavek kompletně splnit, obrací se na vrstvu N-1. Ta pošle další dotaz vrstvě N-2 a tak to pokračuje až k vrstvě 1. Tam jsou vykonány služby na nízké úrovni. Je-li třeba, odpovědi na různé dotazy jsou předány do vrstvy 2, z vrstvy 2, pak do vrstvy 3, až se konečně dostanou k vrstvě N. Je to tzv. top-down komunikace.

2. Tento scénář ilustruje bottom-up komunikaci. Např. když driver

zařízení detekuje vstup, začíná se od vrstvy 1. Pak se pokračuje vrstvou 2, až se dosáhne nejvyšší vrstvy N.

3. Tento scénář popisuje situaci, kdy požadavky prochází pouze

podmnožinou vrstev. Např. požadavek na nejvyšší úrovni postoupí k úrovni N-1, pokud tato úroveň uspokojí jeho požadavek, dál se již nepokračuje. Příkladem může být cache.

4. Tento scénář je podobný předchozímu jen s tím rozdílem, že se

začíná od nejnižší vrstvy a pokračuje se pouze k nejvyšší nutné vrstvě.

5. Zahrnuje dva zásobníky komunikujících vrstev. Tento scénář je

známý z komunikačních protokolů, kde jsou zásobníky známé jako zásobníky protokolů.

Implementace

1. Definovat abstraktní kritérium pro seskupení úloh do vrstev.

2. Stanovit počet abstraktních úrovní podle vašeho abstrakčního kritéria. Každá abstrakční úroveň koresponduje s jednou vrstvou vzoru.

Page 126: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

126

3. Pojmenovat vrstvy a přiřadit úlohy ke každé z nich.

4. Specifikovat služby jednotlivých vrstev.

5. Zjemnit vrstvení.

6. Specifikovat rozhraní pro každou vrstvu.

7. Navrhnout strategii ošetření chyb. Příkladem je TCP/IP protokol – viz předchozí obrázek.

Varianty

Relaxed Layered System je méně restriktivní co se týká vztahů mezi vrstvami. V této variantě může každá vrstva využívat služeb libovolné nižší vrstvy. Může také existovat varianta, kdy jednotlivé vrstvy mohou zviditelnit některé služby pro libovolnou z vyšších vrstev a naopak některé služby poskytovat pouze pro nejbližší vyšší vrstvu. Layered Through Inheritance je varianta, která se nachází v některých objektově orientovaných systémech. V této variantě jsou nižší vrstvy implementovány jako základní třídy.

Známé aplikace

Virtuální stroje. Můžeme mluvit o nižších úrovních jako o virtuálním stroji, který izoluje vyšší vrstvy od detailů nižších vrstev, nebo od různého hardware. Příkladem může být Java Virtual Machine (JVM) definující formát binárního kódu. Kód napsaný v jazyce Java je přeložený do binárního kódu (byte-code), který je nezávislý na platformě (operačním systému). Protože JVM je závislý na softwarové platformě, existují různé JVM pro různé operační systémy a procesory. APIs- Application Programming Interface – rozhraní aplikačních programů. Externí rozhraní softwarové platformy, např. operačního systému, které poskytuje prostředky pro práci nad danou softwarovou platformou. API je obyčejně kolekce funkčních specifikací (volání systémových funkcí). Informační systém (IS) - Rovněž informační systémy bývají vytvářeny pomocí architektury založené na vrstvách.

Výhody

Znovupoužitelnost vrstev, dá se využít v případě dobře definované abstrakce a dobře definovaného a dokumentovaného rozhraní.

Podpora standardizace. Jasně definované a všeobecně akceptované úrovně abstrakce umožňují rozvoj standardizovaných úloh a rozhraní.

Závislosti jsou lokální. Standardizované rozhraní mezi vrstvami obyčejně omezuje změnu kódu pouze na danou vrstvu.

Page 127: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

127

Zaměnitelnost. Daná implementovaná vrstva může být zaměněna sémanticky ekvivalentní implementací bez velkého úsilí.

Slabá místa

Kaskádní změny chování – může nastat při změně chování dané vrstvy. Nízká efektivnost. Architektura založená na vrstvách je obyčejně méně efektivní než monolitická. Problémy se stanovením správné granularity vrstev. Vzor vrstvy se používá ke strukturování aplikací, které mohou být rozloženy na skupiny úloh, kde každá skupina úloh ja na dané úrovni abstrakce.

Page 128: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

128

11.2 Roury a filtry (Pipes and Filters) V této kapitole se dozvíte:

• možnosti využití návrhového vzoru roury a filtry, • jaký je kontext, problém a řešení pro uvedený vzor, • výhody a nevýhody použití.

Návrhový vzor roury a filtry poskytuje strukturu pro systémy, které zpracovávají tok dat (a stream of data). Každý krok zpracování je zapouzdřen v komponentě nazývané filtr . Data jsou posílána prostřednictvím rour (pipes) mezi sousedními filtry. Kombinace filtrů a rour dovoluje vytvořit řadu podobných systémů.

Kontext

Zpracování datových toků.

Problém

Představte si, že vytváříte systém, který musí zpracovat a transformovat tok vstupních dat. Implementace takového systému jako jednotné komponenty není možné z několika důvodů:

• systém bude vytvořen několika vývojáři,

• úloha se přirozeně rozpadá do několika procesních stavů,

• je pravděpodobné, že se požadavky budou měnit.

Požadavky

Budoucí rozšíření systému by mělo být možné změnou procesních kroků, nebo rekombinací kroků, které si je schopen provést i sám uživatel. Malé procesní kroky je snadné použít znova v odlišném kontextu, než velké komponenty. Procesní kroky, které bezprostředně na sebe nenavazují, spolu nesdílejí informace. Zda-li je separace procesních kroků řešitelná, silně závisí na aplikační doméně a problému, který má být řešen. Např. interaktivní, událostmi řízený systém je nemožné rozdělit do sekvenčních kroků.

Řešení

Architektonický vzor roury a filtry dělí řešenou úlohu do několika sekvenčních, procesních kroků. Tyto kroky jsou spojeny pomocí

Page 129: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

129

datových toků (rour) do systému, kde výstupní data daného procesního kroku jsou vstupem do následujícího procesního kroku. Každý procesní krok je implementován jako komponenta filtr . Filtr „konzumuje“ a dodává data inkrementálně – nejdříve spotřebuje celý vstup dat, než vytvoří jakýkoli výstup. Vstupem do takového systému je datový zdroj, jako např. textový soubor, výstupem data sink jako např. soubor, nebo terminál, či animační program. Datový zdroj, datový výstup a filtry jsou spojeny sekvenčně rourami. Každá roura implementuje datový tok mezi přilehlými procesními kroky.

Struktura

Komponenty filtr jsou procesní jednotky. Filtr obohacuje, zjemňuje nebo transformuje vstupní data. Obohacování může představovat nějaké výpočty a přidávání informací, zjemňování dat představuje koncentraci či extrakci informací a transformace představuje dodání dat v jiné reprezentaci. Daný filtr může být kombinací libovolných z těchto základních principů:

• následující prvek si tahá výstupní data z filtru pull varianta,

• předchozí prvek tlačí vstupní data do filtru push varianta,

• nejběžnější, filtr je aktivní ve smyčce tahá si vstupní data a tlačí výstupní data pro další zpracování pull-push varianta.

Obr. 11.3 Sekvenční diagram varianty pull.

read

Data Source Filter 1 pull Filter 2 pull Data Sink pull

read

read

f1

data

f2

data

Page 130: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

130

Obr. 11.4 Sekvenční diagram varianty push. První dva případy označují tzv. pasivní filtry, poslední případ je příkladem aktivního filtru. Aktivní filtr je tvořen buď samostatným programem, nebo vláknem. Roury označují propojení mezi filtry a mezi datovým zdrojem a datovým výstupem. Následuje příklad programu rour a filtrů v jazyce Java. Příklad v jazyce Java. // Nastaví časový limit na provádění programu import java.util.*; public class Timeout extends Timer { public Timeout(int delay, final String msg) { super(true); // Daemon thread schedule(new TimerTask() { public void run() { System.out.println(msg); System.exit(0); } }, delay); } } // Použití rour a vláken pro jednoduchou aplikaci vstupu a // výstupu import java.io.*; import java.util.*;

Data Source push Filter 1 push Filter 2 push Data Sink

f1

write

write

f2

write

Page 131: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

131

class Sender extends Thread { private Random rand = new Random(); private PipedWriter out = new PipedWriter(); public PipedWriter getPipedWriter() { return out; } public void run() { while(true) { for(char c = 'A'; c <= 'z'; c++) { try { out.write(c); sleep(rand.nextInt(500)); } catch(Exception e) { throw new RuntimeException(e); } } } } } class Receiver extends Thread { private PipedReader in; public Receiver(Sender sender) throws IOException { in = new PipedReader(sender.getPipedWriter()); } public void run() { try { while(true) { // Blocks until characters are there: System.out.println("Read: " + (char)in.read()); } } catch(IOException e) { throw new RuntimeException(e); } } } public class PipedIO { public static void main(String[] args) throws Exception { Sender sender = new Sender(); Receiver receiver = new Receiver(sender); sender.start(); receiver.start(); new Timeout(4000, "Terminated"); } }

Výstup aplikace: Read: A Read: B Read: C Read: D Read: E Read: F Read: G Read: H Read: I Terminated

Page 132: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

132

Read: J Read: K Read: L Read: M Read: N

Implementace

Implementace rour a filtrů je přímá. Můžete použít systémovou službu fronty zpráv, nebo UNIXovské roury pro propojení, nebo jinou variantu. 1. Rozdělit systémové úlohy do sekvencí procesních kroků. Každý

takový procesní krok musí záviset pouze na výstupu z předchozího procesního kroku.

2. Definovat datový formát, který bude přenášen rourami.

3. Rozhodnout, jak implementovat každé spojení rour.

4. Navrhnout a implementovat filtry.

5. Navrhnout zpracování chyb.

Výhody

Nejsou třeba žádné „mezi soubory“, ale jsou možné. Flexibilita je umožněna výměnou filtrů. Flexibilita rekombinace. To umožňuje vytvořit novou zpracovávající sekvenci změnou filtrů, nebo přidáním dalších filtrů. Opakované využití komponenty filtru. Rychlé prototypování.

Slabá místa

Sdílení stavových (globálních) informací je nákladné a nepružné. Poněkud složité zpracování chyb. Návrhový vzor roury a filtry slouží ke strukturování systémů, které zpracovávají toky dat. Příkladem mohou být překladače, toky dat příkazů atd. Můžeme se s ním také setkat v aplikacích, které převádějí danou strukturu dat na jinou a k tomu využívají XML.

Page 133: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

133

12 Vzor Zprostředkovatel (Broker) V této kapitole se dozvíte:

• jaké základní komponenty se používají v distribuovaných

aplikacích,

• jaké jsou vztahy mezi těmito komponentami.

Po jejím prostudování budete schopni:

• lépe porozumět a orientovat se ve struktuře distribuovaných

aplikací.

Vzor broker patří mezi distribuované systémy. V této oblasti existují dva základní trendy rozvoje hardwarové technologie:

• počítačové systémy s několika procesory (multiprocesorové systémy);

• místní sítě počítačů spojujících stovky heterogenních počítačů v jeden systém.

Co jsou výhody distribuovaných systémů? Ekonomika. Počítačové sítě, které zahrnují jak PC počítače, tak pracovní stanice poskytují lepší poměr cena/výkon než velké mainframy. Výkon a škálovatelnost. Podle firmy Sun je počítačová síť počítač, distribuované aplikace jsou schopny využívat zdroje dostupné v síti počítačů. Některé aplikace jsou již ve své podstatě distribuované, např. databázové aplikace, které jsou vytvořeny podle modelu klient/server. Spolehlivost. V mnoha případech dochází k výpadku počítačů na síti, bez vlivu na zbytek systému (ostatní počítače). Distribuované aplikace však mají také některé významné nedostatky oproti centralizovaným systémům. Distribuované aplikace potřebují zcela nové a odlišné programové vybavení, než centralizované systémy. Architektonický vzor broker může být použit na strukturování distribuovaných programových systémů s oddělenými komponentami, které jsou v interakci prostřednictvím vzdáleného volání služeb (remote service invocations). Komponenta broker je odpovědná za

Page 134: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

134

koordinaci komunikace, jako je odesílání požadavků, rozesílání výsledků a výjimek.

Kontext

Vaše prostředí je distribuované a možná i heterogenní s nezávislými kooperujícími komponentami.

Problém

Vytvoření komplexního programového systému jako množiny oddělených a mezi sebou operujících komponent. Výsledkem bude větší flexibilita, lepší údržba a zaměnitelnost. Avšak pro distribuované komponenty komunikující mezi sebou jsou potřebné nějaké prostředky mezi procesorové komunikace (IPC). Pokud by komponenty komunikovaly přímo mezi sebou, výsledný systém by čelil několika závislostem a omezením. Například by byl systém závislý na použitých komunikačních mechanismech, klienti by potřebovali znát umístění serverů a v mnoha případech by bylo řešení omezeno pouze na jeden programovací jazyk. Jsou potřebné také služby pro přidání, odstranění, změnu aktivaci a umístění komponent. Aplikace, které využívají těchto služeb, by neměly být závislé na specifikačních detailech systému, aby garantovaly přenositelnost a interoperabilitu. Z pohledu vývojáře programového vybavení by neměl být podstatný rozdíl mezi vývojem programového vybavení pro centralizované systémy a pro distribuované systémy. Aplikace, která používá objekt, by měla mít možnost si prohlédnout rozhraní poskytované jiným objektem. Nemusí nic vědět o implementačních detailech, nebo o umístění daného objektu. Při použití vzoru broker je třeba porovnat (zohlednit) následující požadavky:

• komponenty by měly být schopny přístup na služby poskytované jinými komponentami prostřednictvím volání vzdálené služby transparentní k umístění.

• potřebujete měnit, přidávat, odstraňovat komponenty za běhu programu.

• Architektura by měla ukrývat detaily implementačních specifik od uživatelských komponent a služeb.

Page 135: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

135

Řešení

Zavést komponentu broker, která docílí lepší oddělení klientů a serverů. Servery se registrují u komponenty broker a své služby nabízejí klientům prostřednictvím metod rozhraní. Klienti mají přístup k funkcionalitě serverů zasláním požadavků prostřednictvím komponenty broker. Úlohou komponenty broker je najít umístění vhodného serveru, odeslat požadavek na tento server a rozeslat výsledky a výjimky zpět klientovi. Použitím vzoru broker může mít aplikace přístup k distribuovaným službám jednoduše tím, že zašle volání služby vhodnému objektu. Vyhne se tím pracím s nízkoúrovňovou meziprocesorovou komunikací (low-level inter process communication). Vzor broker snižuje složitost, která je spojena s distribuovanými aplikacemi, protože vytváří distribuovanost transparentní pro vývojáře. Svého cíle dosahuje zavedením objektového modelu, v němž jsou distribuované služby zapouzdřeny uvnitř objektů. Vzor broker takto nabízí cestu k integraci dvou základních technologií:

• distribuované technologie;

• objektové technologie.

Struktura

Vzor broker sestává ze šesti typů komponent: clients, servers, brokers, bridges, client-side-proxies, server-side-proxies.

Obr. 12.1 Struktura vzoru broker s využitím diagramu tříd UML

Page 136: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

136

Server

Komponenta server implementuje objekty, které vystavují svoji funkčnost prostřednictvím rozhraní, které se skládá z operací a atributů. Toto rozhraní je dostupné buď prostřednictvím interface-definition-language (IDL), nebo prostřednictvím binárního standardu. Rozhraní typicky seskupují sémanticky blízké funkcionality. Existují dva druhy serverů:

• servery poskytující běžné služby mnoha aplikačním doménám;

• servery implementující specifickou funkcionalitu pro jednu aplikaci, doménu nebo úlohu.

Client Komponenty client jsou aplikace, které přistupují ke službám alespoň jednoho serveru. K vyvolání vzdálené služby klienti zašlou požadavek brokeru. Po provedení operace obdrží od brokeru výsledky, nebo výjimky. Interakce mezi komponentami klientů a serverů je založena na dynamickém modelu, který znamená, že servery mohou také působit jako klienti. Tento dynamický interakční model se liší od tradičního označení klient/server výpočtů v tom, že role klientů a serverů nejsou staticky definovány. Z pohledu implementace můžeme považovat klienty za aplikace a servery za knihovny. Všimněte si, že klienti nepotřebují znát umístění serverů, ke kterým přistupují. To je důležitá vlastnost, protože dovoluje přidání nových služeb a přesun existujících služeb na jinou adresu dokonce za běhu systému. Broker Komponenta broker je posel zodpovědný za rozeslání požadavků od klientů k serverům, stejně jako rozeslání odpovědí a výjimek zpět klientům. Komponenta broker musí mít nějaké prostředky pro adresaci příjemce požadavku založeném na jednotném systémovém identifikátoru. Broker poskytuje API (Application Programming Interfaces) klientům a serverům, jenž zahrnuje operace pro registraci serverů a pro vyvolání metod serveru. Když přijde požadavek na server, který je udržován místní komponentou broker, tak broker předá požadavek přímo serveru. Není-li právě server aktivní, broker ho aktivuje. Všechny odpovědi a výjimky z prováděné služby jsou zaslány komponentou broker zpět klientovi, který vyslal požadavek. Je-li daný server hostem jiné komponenty broker, místní broker nalezne cestu (route) ke vzdálenému brokeru a zašle požadavek používajíc tuto cestu. Je zde potřeba, aby brokery navzájem komunikovaly.

Page 137: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

137

V závislosti na požadavcích celého systému, mohou být do komponenty broker integrovány dodatečné operace jako name service nebo marshaling support.

Client-side proxies

Client-side proxies reprezentuje vrstvu mezi komponentami client a broker. Tato dodatečná vrstva poskytuje transparentnost v tom, že vzdálený objekt se klientovi jeví jako lokální. Konkrétně proxies umožňují ukrývání detailů implementace od klienta jako např.:

• meziprocesorový komunikační mechanismus používaný pro přenos zpráv mezi klienty a brokery;

• tvorba a rušení paměťových bloků;

• řazení (marshaling) parametrů a výsledků. V mnoha případech client-side-proxies převádějí objektový model, specifikovaný jako část brokeru, do objektového modelu programovacího jazyka použitého k implementaci klienta.

Server-side proxies

Server-side proxies je analogické k client-side proxies. Rozdílnost je v tom, že server-side proxies jsou zodpovědné za příjem požadavků, rozbalování příchozích zpráv a volání odpovídajících služeb. Jsou také použity pro řazení výsledků a výjimek před odesláním klientovi.

Bridges

Bridges jsou volitelné komponenty použité k ukrývání implementace detailů, když spolu komunikují dvě komponenty broker. Nejpoužívanější scénář použití ilustruje případ, kdy se komponenta server registruje u komponenty místní (lokální) broker: • komponenta broker je odstartovaná v inicializační fázi systému.

Vstoupí do smyčky, ve které očekává příchod zpráv.

• uživatel, nebo nějaká jiná entita odstartuje komponentu server aplikace. Po inicializaci se server zaregistruje u komponenty broker.

• když komponenta broker dostane požadavek registrace od serveru, vybere si z ní všechny potřebné informace a ty si uloží. Tyto informace jsou využity pro nalezení a aktivaci příslušného serveru. Zpět serveru je poslána potvrzovací zpráva.

Page 138: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

138

• Po obdržení potvrzovací zprávy od komponenty broker, server vstoupí do čekací smyčky na požadavky od klientů.

Obr.12.2 Dynamika vzoru broker zobrazena s použitím sekvenčního diagramu UML

Implementace

1. Definovat objektový model, nebo použít stávající model.

2. Definovat, který druh komponentní interoperability bude systém nabízet. Interoperabilita může být buď specifikovaná binárním standardem, nebo interface definition language (IDL). Binární standard vyžaduje podporu programovacího jazyka a je používán firmou Microsoft.

3. Specifikovat API, které komponenta brokeru poskytuje pro spolupráci s klienty a servery.

4. Použít objekty proxy k ukrytí implementačních detailů od klientů a serverů.

5. Navrhnout komponentu broker.

6. Vytvořit překladač IDL.

Client ServerServer-side ProxyBrokerClient-side Proxy

sendRequest

packData

forwardRequest

findServer

callService

unpackData

runService

packDataforwardResponse

findClientreturn

unpackData

callServer

result

Page 139: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

139

Varianty

Direct Communication Broker System. V této variantě mohou klienti komunikovat se servery přímo. Broker řekne klientům, který komunikační kanál server poskytuje. Klient potom může vytvořit přímé spojení se serverem. Message Passing Broker Systém. Tato varianta je vhodná pro systémy, které se zaměřují na rozesílání dat.

Trader system

Požadavek klienta je zaslán přímo konkrétnímu serveru. V této variantě musí broker znát, který server poskytuje dané služby a zaslat požadavek přímo na daný server. Client-side proxies proto používají identifikátory služeb.

Adapter Broker Systém

V této variantě je rozhraní brokeru ukryto v komponentě server, v dodatečné vrstvě adapter, viz kap. 7.1. Tato vrstva se stará o registraci serverů a interakci mezi nimi.

Callback Broker Systém

Místo implementace aktivního komunikačního modelu, ve kterém klienti vytvářejí požadavky na servery, je možné použít tzv. reaktivní model, který je řízen událostmi a nedělá žádný rozdíl mezi klienty a servery. Když přijde událost, broker vyvolá zpětné volání metody komponenty, která je registrovaná, že bude reagovat na tuto událost. Ta pak může následně ještě vyvolat další události.

Použití

Tento vzor je použit ke specifikaci Common Object Request Broker Architecture (Corba). To je objektová technologie pro distribuované objekty hererogenních systémů. Pro podporu interoperability je definován interface definition language (IDL). OLE 2.x od Microsoft. Tato technologie poskytuje další příklad použití vzoru broker. Pro zpřístupnění a vystavení rozhraní serveru tato technologie používá binární standard.

Page 140: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

140

Výhody

• Transparentnost umístění je umožněna tím, že komponenta broker je zodpovědná za vyhledání serveru podle jedinečného identifikátoru.

• Zaměnitelnost a rozšiřitelnost komponent.

• Portabilita systému s komponentou broker.

• Interoperabilita mezi různými systémy s komponentou broker (neplatí u OLE, je to standard „MS“).

• Znovupoužitelnost. Slabá místa Omezená výkonnost. Nízká odolnost vůči chybám. Testování a ladění. Návrhový vzor broker se využívá v distribuovaných systémech. Skládá se celkem ze 6 komponent. Ke své činnosti využívá služby vzdáleného volání (v Javě RMI). Zabezpečuje předání dotazu klienta odpovídajícímu serveru.

Page 141: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

141

13 Adaptivní systémy

13.1 Microkernel

V této kapitole se dozvíte:

• k čemu slouží daný návrhový vzor,

• jaké jsou komponenty tohoto návrhového vzoru,

• k čemu se deklarují a nač se používají komponenty internal server a external server,

• jak je možné prakticky využít návrhový vzor Reflection.

Tento vzor se používá pro programové systémy, které musí být schopné se adaptovat na měnící se požadavky systému. Odděluje jádro minimálních funkcí od rozšířené funkcionality a uživatelsky specifických částí. Komponenta microkernel také slouží jako socket (zásuvka) pro vložení těchto rozšíření a koordinaci jejich spolupráce.

Kontext

Vývoj několika aplikací které používají podobné programové rozhraní, které je vytvořeno na stejném funkcionálním jádře.

Problém

Vývoj programového vybavení pro aplikační doménu, který se musí vyrovnat se širokým spektrem podobných standardů a technologií není triviální úloha. V úvahu se musejí vzít následující požadavky: • Aplikační platforma se musí vyrovnat s neustálým hardwarovým a

softwarovým vývojem.

• Aplikační platforma by měla být přenositelná, rozšiřitelná a adaptovatelná, aby umožnila snadnou integraci nových technologií.

Úspěch takovýchto aplikačních platforem dále závisí na jejich schopnosti provozovat aplikace psané pro existující standardy. K podpoře širokého spektra aplikací je třeba více než jeden „pohled“ funkcionality základní aplikační platformy. Jinými slovy, aplikační platforma jako operační systém nebo databáze by měla být také schopná emulovat jiné aplikační platformy, které patří ke stejné aplikační doméně. To vede k následujícím požadavkům:

• Aplikace ve vaší aplikační doméně potřebují podporovat různé, ale podobné aplikační platformy.

Page 142: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

142

• Aplikace musí být kategorizovány do takových skupin, které používají stejné funkcionální jádro různým způsobem, požadujíc základní aplikační platformu (microkernel), aby emulovala existující standardy.

Aplikační platforma, která poskytuje funkcionální jádro domény je exkluzivním (nedělitelným) zdrojem pro její klienty. Abychom se vyhnuli problémům spojeným s provozem a garantováním škálovatelnosti, naše řešení musí vzít v úvahu ještě jeden požadavek: • Funkcionální jádro aplikační platformy by mělo být rozdělené do

komponent s minimální velikostí paměti a služeb, které spotřebují minimální kapacitu procesoru.

Řešení

Zapouzdřete základní služby vaší aplikační platformy do komponenty microkernel. Tato komponenta zahrnuje funkcionalitu, která umožňuje jiným komponentám běžet v oddělených procesech a komunikovat spolu. Komponenta microkernel je také odpovědna za údržbu (podporu) systémových zdrojů jako jsou soubory a procesy. Navíc poskytuje rozhraní, které umožňuje jiným komponentám přístup k funkcím komponenty microkernel. Jádro funkcionality, které nemůže být implementováno uvnitř komponenty microkernel, protože by rozšířilo rozsah a složitost komponenty microkernel, by mělo být vyděleno do interních serverů. Externí servery implementují jejich vlastní „pohled“ základní komponenty microkernel. Ke konstrukci tohoto „pohledu“ využívají mechanismus dostupný prostřednictvím rozhraní komponenty microkernel. Každý externí server je samostatný proces, který sám reprezentuje aplikační platformu. Takto se na komponentu microkernel můžeme dívat jako na aplikační platformu, která integruje jiné aplikační platformy. Klienti komunikují s komponentami externích serverů tím, že používají prostředky poskytované komponentou microkernel.

Struktura

Vzor microkernel definuje pět druhů spolupracujících komponent: • internal servers (interní servery)

• external servers (externí servery)

• adaptery (emulátory)

• klienty

• microkernel

Page 143: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

143

Komponenta microkernel reprezentuje hlavní komponentu vzoru. Implementuje základní služby jako jsou komunikační prostředky, nebo obsluha zdrojů. Jiné komponenty staví na všech, nebo některých z těchto základních služeb. Dělají to nepřímo prostřednictvím jednoho nebo více rozhraní, které nabízí komponenta microkernel. Komponenta microkernel zapouzdřuje mnoho systémově (hardwarově) specifických závislostí. Klienti komponenty microkernel pouze vidí konkrétní „pohledy“ základní aplikační domény a specifika platformy. Ve stručnosti komponenta microkernel implementuje atomické (dílčí) služby, které nazýváme mechanismy. Tyto mechanismy slouží jako základní báze, na které jsou konstruovány složitější funkce nazývané politiky (policies). Komponenta interní server, známý též jako subsystém, rozšiřuje funkcionalitu komponenty microkernel. Je reprezentovaný oddělenou komponentou, která poskytuje dodatečnou funkcionalitu. Microkernel vyvolává funkcionalitu interních severů přes požadavek služeb. Interní servery mohou takto zapouzdřit některé závislosti základního hardware, nebo software systému. Např. ovladače různých grafických karet. Jedním z cílů by mělo být udržet komponentu microkernel co možná nejmenší z důvodů požadavků na paměť. Dalším cílem je poskytnout mechanismy, které se vykonávají rychle, aby omezily čas běhu služeb. Externí server je komponenta, která používá microkernel pro implementaci vlastního „pohledu“ základní aplikační domény. Externí servery nabízejí svoji funkcionalitu exportováním rozhraní stejným způsobem, jak to dělá samotná komponenta microkernel. Každý z těchto externích serverů běží v samostatném procesu. Požadavky služeb získá z klientských aplikací použitím komunikačních prostředků poskytovaných komponentou microkernel, interpretuje tyto požadavky, vykonává vhodné služby a vrací výsledky klientům. Klient je aplikace, která je asociovaná přesně s jedním externím serverem. Pouze zpřístupňuje programové rozhraní poskytované externím serverem. Problém vzniká, když klient potřebuje přístup k rozhraní svého externího serveru přímo. Každý klient musí používat dostupné komunikační prostředky, aby komunikoval s externími servery. Každá komunikace s externím serverem musí být neměně zakódovaná do kódu klienta. Takové úzké propojení mezi klienty a servery způsobuje mnoho nevýhod: • takový systém nepodporuje dobře zaměnitelnost;

• pokud externí servery emulují existující aplikační platformy, klienti aplikací vytvořené pro tyto platformy nebudou moci pracovat s modifikacemi.

Page 144: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

144

Proto se mezi klienty a externími servery zavádí rozhraní, které chrání klienty od přímých závislostí tzv. adapter. Adaptery, někdy nazývané jako emulátory, reprezentují toto rozhraní. Dovolují přístup klientům ke službám externích serverů a zachovávají portabilitu. Jsou částí klientského adresového prostoru. Pokud externí server implementuje existující aplikační platformu, odpovídající adapter napodobuje programové rozhraní platformy. Klienti napsáni pro emulovanou platformu mohou být proto kompilováni a spouštěni bez modifikací. Kdykoli klient požaduje službu externího serveru, je úlohou adapteru zaslat volání na odpovídající server. Z toho důvodu adapter používá komunikační služby poskytované komponentou microkernel. Následující diagram ukazuje statickou strukturu systému microkernel. Jeho centrální komponenta microkernel spolupracuje s externími servery, interními servery a adaptery. Každý klient je asociován s adapterem, který je použitý jako most mezi klientem a externím serverem. Interní servery mohou být zpřístupněny pouze komponentou microkernel.

Obr. 13.1 Základní struktura vzoru Microkernel

Implementace

1. Analyzovat aplikační doménu.

2. Analyzovat externí servery.

3. Kategorizovat služby.

4. Oddělit kategorie na služby, které by měly být části microkernelu a ty, které by měly být dostupné jako interní servery.

5. Strukturovat komponenty microkernelu.

+receiveRequest()+dispatchRequest()+executeService()

External Server

+executeMechanism()+initCommunication()+findReceiver()+createHandler()+sendMessage()+callInternalServer()

Microkernel

+executeService()+receiveRequest()

InternalServer

+callService()+createRequest()

Adapter

+doTask()

Client

-calls-activates

-calls service

-initialCommunication

-sendRequest

Page 145: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

145

6. Specifikovat programové rozhraní microkernelu.

7. Implementovat jednotlivé komponenty.

Dynamické chování vzoru microkernel

Na obrázku je uvedena varianta, kdy externí server nepřistupuje k rozhraní komponenty microkernel. Scénář je následující:

• klient požádá o službu externího serveru prostřednictvím komponenty adapter;

• adapter se po obdržení požadavku na službu dotazuje komponenty microkernel na komunikační spojení s externím serverem;

• komponenta microkernel určí fyzickou adresu externího serveru a vrací ji adapteru;

• poté adapter vytvoří přímé komunikační spojení s externím serverem;

• adapter zašle požadavek na externí server s využitím vzdáleného volání procedur (RPC);

• externí server po rozbalení zprávy deleguje její vykonávání na jednu ze svých metod. Po zkompletování požadavku služby externí server pošle výsledky a stavové informace zpět adapteru;

• adapter pošle zpět výsledky klientovi, který pak dále pokračuje ve vykonávání svých dalších aktivit.

Page 146: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

146

Obr. 13.2 Sekvenční diagram práce vzoru Microkernel Další scénář ukazuje chování vzoru microkernel, když externí server požaduje službu, kterou poskytuje interní server.

• externí server posílá svůj požadavek komponentě microkernel;

• během vykonávání metody zašle komponenta microkernel požadavek na službu internímu serveru;

• po vykonání požadavku zasílá komponenta interní server výsledky zpět komponentě microkernel;

• komponenta microkernel vrací výsledky zpět komponentě externí server;

• po obdržení výsledků pokračuje externí server ve svých dalších aktivitách.

Client Adapter Microkernel External Server

callService

createRequest

initCommunication

findReceiver

receiveRequest

dispatchRequest

executeService

Page 147: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

147

Obr. 13.3 Sekvenční diagram zobrazující spolupráci Microkernelu s Externím a Interním Serverem Na obrázku je uvedeno schéma druhého scénáře.

Implementace

1. Analyzovat aplikační doménu.

2. Analyzovat externí servery.

3. Kategorizovat služby.

4. Rozdělit kategorie na služby, které by měly být částí komponenty microkernel a ty, které budou dostupné prostřednictvím interních serverů.

5. Určit strategie pro přenos a zpětné získání výsledků.

6. Strukturovat komponentu microkernel.

7. Navrhnout a implementovat interní servery.

8. Navrhnout a implementovat externí servery.

9. Implementovat adaptery.

External Server Microkernel Internal Server

executeMechanism

callInternalServer

receiveRequest

executeService

Page 148: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

148

Varianty

Systém microkernel s nepřímým spojením Klient-server. Klient, který chce poslat požadavek nebo zprávu externímu serveru, požádá komponentu microkernel o komunikační kanál. Poté co je tento kanál vytvořen, komunikace klient-server probíhá nepřímo prostřednictvím microkernelu. Distribuovaný microkernel systém. V této variantě může komponenta microkernel působit jako základní (páteřní) část, zodpovědná za posílání zpráv vzdáleným počítačům, nebo obdržení zpráv od nich. Každý počítač v distribuovaném systému používá svou vlastní implementaci komponenty microkernel.

Výhody

Portabilita. Flexibilita a rozšiřitelnost. Škálovatelnost. Spolehlivost. Transparentnost.

Úzká místa

Výkonnost. Monolitický programový systém bude mít vždy větší výkonnost. Složitost návrhu a implementace. Návrhový vzor microkernel je použitelný v programových systémech, které musí být schopny se rychle přizpůsobit měnícím se systémovým požadavkům. Tato komponenta může dále sloužit jako zásuvka pro další komponenty.

Page 149: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

149

13.2 Reflexe (Reflection) Poskytuje mechanismus pro změnu struktury chování softwarových systémů dynamicky (za běhu programu). Podporuje modifikaci základních aspektů jako je typ struktury a mechanismus volání funkcí. Tento vzor dělí aplikaci na dvě části:

• meta úroveň poskytuje informace o vybraných vlastnostech systému a umožňuje tak, aby si SW uvědomoval sám sebe;

• základní úroveň obsahující aplikační logiku.

Aplikace je vytvořena na meta úrovni (nad meta úrovní). Změny informací v meta úrovni ovlivňují následně chování základní úrovně. Kontext: Budování programových systémů, které apriori podporují svoji vlastní modifikaci. Problém: Programové systémy se vyvíjejí v čase a měly být otevřené modifikacím a změnám na základě změny technologií a požadavků. Pro programové systémy je lepší specifikovat jejich architekturu otevřenou k modifikacím a rozšíření. Při řešení problému působí následující „síly“:

• Změna software je namáhavá (úmorná), náchylná k chybám a drahá.

• Softwarové systémy schopné adaptace mají obyčejně složitou vnitřní strukturu. Implementace služeb aplikace je rozšířena na mnoho malých komponent s odlišným vzájemným vztahem

• Mnoho technik, které jsou potřebné pro udržení systému schopného akceptovat změny např. parametrizace, subclassing, copy paste, tím nešikovnější je pak modifikace.

• Změny SW mohou mít libovolný rozsah.

• Mohou být měněny i základní aspekty celého systému Řešení: Vytvořit „samouvědomující SW“ (self aware) a učinit některé aspekty této struktury a chování dosažitelnými pro adaptaci a změny. Vede to ke struktuře: meta úroveň a základní úroveň. Meta úroveň Meta úroveň poskytuje self-representation programové aplikace a tím poskytuje vědomosti o její vlastní struktuře a chování. Meta objekty zapouzdřují a prezentují informace o programové aplikaci

Page 150: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

150

Základní úroveň Základní úroveň definuje aplikační logiku. Implementace používá metaobjekty, aby udržela nezávislé ty aspekty, u kterých je pravděpodobnost změn. Např. komponenty základní úrovně mohou spolu vzájemně komunikovat prostřednictvím metaobjektu, který implementuje konkrétní uživatelsky definovaný mechanismu volání funkcí. Existuje specifikované rozhraní pro manipulaci s metaobjekty. Struktura Meta úroveň je složena s z množiny metaobjektů. Každý metaobjekt zapouzdřuje vybrané informace o jednotlivých aspektech struktury, chování, nebo stavu základní úrovně.

Jsou tři zdroje těchto informací:

1. Informace jsou poskytovány za běhu prostředím systému.

2. Informace mohou být definovány uživatelem – anotace.

3. Informace mohou být zjištěny ze základní úrovně za běhu programu – aktuální stav výpočtu.

Všechny metaobjekty spolu poskytují vlastní reprezentaci (self-representation) aplikace. Metaobjekty vytváří informace, které jsou jinak pouze dostupné implicitně, explicitně přístupné a modifikovatelné. Např. v distribuovaných systémech mohou existovat metaobjekty, které poskytují informace o fyzickém rozmístění komponent základní úrovně. Zjištění zda komunikující partner je lokální nebo globální. Reflexe v Javě – balíček java.lang.reflect import java.lang.reflect.*; public class DumpForMethod { public static void main(String args[]) { try { // parametr zkoumane tridy predan // jako args[0] // java DumpForMethod Bod Class c = Class.forName(args[0]); Method m[] = c.getDeclaredMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); } catch (Throwable e) { System.err.println(e); } } }

Page 151: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

151

Výpis, který dostaneme po zadání: java DumpForMethod java.util.Stack Použijeme reflexi metod na knohovní třídu java.util.Stack, na kterou se můžete podívat do javovského prohlížeče. public java.lang.Object java.util.Stack.push( java.lang.Object) public synchronized java.lang.Object java.util.Stack.pop() public synchronized java.lang.Object java.util.Stack.peek() public boolean java.util.Stack.empty() public synchronized int java.util.Stack.search(java.lang.Object)

Využití reflexe v Javě Nastavení používání reflexe: Tři kroku vedoucí k využití balíčku java.lang.reflect. 1. Získat class objekt java.lang.Class, se kterou se bude manipulovat.

java.lang.Class se používá k reprezentaci tříd a rozhraní v běžícím javovském programu. • Jednou z možností získání objektu třídy je:

Class c = Class.forName("java.lang.String");

získá se Class object pro řetězce Jiný způsob:

Class c = int.class; // or Class c = Integer.TYPE;

• to vede k získání informací o základních typech.

2. Druhým krokem je zavolat metodu např. getDeclaredMethods(), k získání seznamu všech metod deklarovaných ve třídě.

3. Po té co jsou informace k dispozici, je třetím krokem využití API

reflexe k manipulaci s informacemi. Např. sekvence příkazů:

Class c = Class.forName("java.lang.String"); Method m[] = c.getDeclaredMethods(); System.out.println(m[0].toString());

Zobrazí textově první informaci uvedenou v řetězci.

Page 152: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

152

Simulace operátoru instance() class A {} public class instance1 { public static void main(String args[]) { try { Class cls = Class.forName("A"); boolean b1 = cls.isInstance(new Integer(37)); System.out.println(b1); boolean b2 = cls.isInstance(new A()); System.out.println(b2); } catch (Throwable e) { System.err.println(e); } } }

Vyhledání metod dané třídy import java.lang.reflect.*; public class Method1 { private int f1( Object p, int x) throws NullPointerException { if (p == null) throw new NullPointerException(); return x; } public static void main(String args[]) { Method1 method1 = new Method1(); try { // Class cls = Class.forName("Method1"); Class cls = method1.getClass(); Method methlist[] = cls.getDeclaredMethods(); for (int i = 0; i < methlist.length; i++) { Method m = methlist[i]; System.out.println("name = " + m.getName()); System.out.println("decl class = " + m.getDeclaringClass()); Class pvec[] = m.getParameterTypes(); for (int j = 0; j < pvec.length; j++) System.out.println(" param #" + j + " " + pvec[j]); Class evec[] = m.getExceptionTypes(); for (int j = 0; j < evec.length; j++) System.out.println("exc #" + j + " " + evec[j]); System.out.println("return type = " + m.getReturnType()); System.out.println("-----"); } }

Page 153: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

153

catch (Throwable e) { System.err.println(e); } } }

Výpis výsledků: name = f1 decl class = class reflection.Method1 param #0 class java.lang.Object param #1 int exc #0 class java.lang.NullPointerException return type = int ----- name = main decl class = class reflection.Method1 param #0 class [Ljava.lang.String; return type = void -----

Reflexe je velmi důležitý a účinný prostředek ke tvorbě programového řešení otevřeného změnám. Rozdělením aplikace na metaúroveň a základní úroveň právě takové řešení umožňuje. V této části jsou také uvedeny třídy a metody javovské knihovny Reflection, která poskytuje řadu zajímavých a účinných tříd a metod.

Page 154: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

Příklady k samostatnému zpracování a odeslání emailem ke kontrole. 1. V kapitole 7.1 je uveden

ukázce k tomuto vzoru je uveden adaptér Vytvořte opačný adapter (metody Iterátoru a ty je třeba adaptovat do Enumeration.

2. Podle návrhového vzoru Stav (kapitola 9) vytvoaplikaci stavů procesů vnekonečnou smyčku. Jsou tobrázek pod textem). Otestujte funk

připravený

běžící

runbreak

wait

ready

3. Vytvořte programovou aplikaci na vzor jednoduchá továrna, která bude vytvářet podle parametru instanci od podtTřída Kachna má podtřGumovaKachna. Divoká Kachní vábnička se použije kkachna se použije při koupání. Otestujte funk

4. Uvedený příklad se týká využití reflexe vjava.lang.reflect). VytvořDivokaKachna (předchozí pvšechny deklarované (zděrozhraní.

154

samostatnému zpracování a odeslání emailem ke

.1 je uveden návrhový vzor Adaptér. V programové tomuto vzoru je uveden adaptér EnumerationIterator.

ný adapter (IteratorEnumeration). Klient využívá metody Iterátoru a ty je třeba adaptovat do Enumeration.

Podle návrhového vzoru Stav (kapitola 9) vytvořte jednoduchou ů v počítači. Předpokládejme, že se jedná o Jsou tři stavy a čtyři přechody mezi stavy (viz

obrázek pod textem). Otestujte funkčnost řešení.

čekající

wait

ready

te programovou aplikaci na vzor jednoduchá továrna, která et podle parametru instanci od podtříd třídy Kachna.

má podtřídy: DivokaKachna, KachniVabnička, Divoká kachna se použije jako maso na piknik,

ka se použije k lovu na divoké kachny a Gumová i koupání. Otestujte funkčnost řešení.

íklad se týká využití reflexe v Javě (knihovna ). Vytvořte třídu, která od objektu třídy

edchozí příklad) zjistí a vypíše: název třídy, všechny deklarované (zděděné) metody, všechna implementovaná

Page 155: APLIKACE NÁVRHOVÝCH VZOR - Ostravská univerzitahunka/vyuka/javaOOP/XXOBJP.pdf · aplikace nÁvrhovÝch vzor Ů ur Čeno pro vzd ĚlÁvÁnÍ v akreditovanÝch studijnÍch programech

155

LITERATURA

[1] Freeman, E., Freeman, E. Head First Design Patterns. O’Reilly

2004

[2] Metsker, S.J., Wake, W.C. Design Patterns in Java. Addison-Wesley 2006

[3] Eckel, B. Thinking in Design Patterns. www.bruceeckel.com [10. 3. 2008]

[4] Buschman, F., Meunier, R. a kol. Pattern Oriented Software Architecture. WILEY 1998


Recommended