SW_04
Návrhové vzory (Design Patterns)
1
Definice vzoru • Každý vzor popisuje problém, který nastává opakovaně v našem prostředí, • a potom popisuje podstatu řešení tohoto problému • takovým způsobem, že můžete použít řešení milionkrát bez toho, že byste dělali stejnou věc dvakrát. • Christopher Alexander 2
Světlo z obou stran každého pokoje • Když mají lidé na výběr, vždy tíhnou k takovým pokojům, do kterých přichází přirozené světlo alespoň ze dvou stran. • Při projektování domu, je nejlepší umístit každý pokoj tak, aby měl vnější prostor alespoň ze dvou stran, s dostatkem oken, které by zachycovaly přirozené světlo více než z jednoho směru.
3
Literatura
• Gamma E., Helm R.: Degign Patterns. Addison -Wesley 1995 • (český překlad – Návrh programů pomocí vzorů. Grada 2003)
4
Základní literatura
• Pecinovský R.: Návrhové vzory. Computer Press 2007 • Bruce Eckel: Thinking in Patterns. www.bruceeckel.com • Buchmann F.: Pattern-Oriented Software Architecture A System of Patterns. Willey 1998 • Metsker S.J. Wake W.C.: Design Patterns in Java. Addison-Wesley. 2006 5
Existující knihy k Business Patterns
6
Úvod • Vzory pro návrh, návrhové vzory (Design Patterns) – souvislost s objektově orientovanou analýzou a návrhem. • Klíčový poje – znovupoužitelnost (reusability) • Znovupoužitelnost v oblasti objektově orientovaných jazyků – části software, které se použijí buď přímo, nebo po malém přizpůsobení – využívá se dědičnosti.
7
Úvod • Znovupoužitelnost je v oblasti objektově orientovaných jazyků použita na úrovni implementace zdrojového kódu. • Vzory pro návrh vycházejí z práce zkušeného návrháře, ten nezačíná svoji práci od nuly, ale pokud se mu nějaký způsob řešení osvědčil – snaha používat ho znovu a vylepšovat ho. • Znovupoužitelnost v oblasti návrhových vzorů znamená znovupoužití znalostí získaných v průběhu návrhu. 8
Úvod • Znovupoužitelnost je možné chápat v několika úrovních: – dědičnost a business objekty (aplikační objekty) – znovupoužitelnost na implementační úrovni – návrhové vzory – znovupoužitelnost v oblasti návrhu nabytých znalostí
9
Co je vzor • Vzor určuje opakující se (opětovný) návrh problému, který vzniká ve specifických situacích a presentuje nějaké jejich řešení. • Vzory reprezentují existující, dobře prověřené zkušenosti návrhu. • Vzory identifikují a specifikují abstrakce, které jsou nad úrovní jednotlivých tříd a instancí nebo komponent. • Vzory poskytují společný slovník a pochopení principů návrhu. 10
Co je vzor • Vzory jsou prostředky pro dokumentaci softwarové architektury. • Vzory pomáhají obsáhnout složitost software a umožňují srozumitelnost a pochopitelnost software .
11
Co je návrhový vzor • Návrhový vzor je libovolná znalost, která vznikla při návrhu programového systému abstrakcí od specifických konkrétních podmínek. • Nutno doplnit konkrétní fyzickou podobu takovéto znalosti. • Forma zápisu vzoru pro návrh není pevně daná, je závislá na tom, čeho se zkušenost kterou chceme vyjádřit návrhovým vzorem týká. 12
Forma zápisu • Model použitý při návrhu potřebuje slovní komentář, který přesněji specifikuje použité prvky a vazby v modelu a umožní pochopení problému. • Nejdůležitějšími charakteristikami tohoto popisu kromě jiných bývají: • kontext – situace, která způsobuje vznik problému • problém – opakující se problém vznikající v daném kontextu
• řešení – prověřené řešení problému (někdy alespoň částečné) 13
Kontext, problém a řešení • kontext: – vytvoření programového vybavení s uživatelským grafickým rozhraním
• problém: – obyčejně je problém reprezentovaný množinou někdy protichůdně působících sil např.: – uživatelské rozhraní by mělo být snadno modifikovatelné – funkční jádro programového vybavení by nemělo být postiženo změnou uživatelského rozhraní
14
Kontext, problém a řešení – všeobecně „síly“ pomáhají objasnit problém řešení z různých úhlů pohledu a pomáhají tak mu porozumět ve všech detailech – „síly“ mohou působit doplňkově nebo protichůdně
• řešení: – ukazuje, jak řešit opakující se problém nebo lépe, jak vyrovnat síly, s ním sdružené.
15
Řešení •
v softwarové architektuře řešení zahrnuje dva aspekty: 1. Každý vzor specifikuje jistou strukturu a prostorovou konfiguraci prvků. Řešení naznačeného problému je v rozdělené aplikace na vlastní zpracování, vstupy, výstupy (MVC). To představuje statickou strukturu řešeného problému. 2. Každý vzor specifikuje chování za běhu (run-time). Např. MVC – controller dostane vstup od myši, klávesnice. Událost je transformována na požadavky služeb. 16
Řešení • Tyto požadavky služeb jsou zaslány buď modelu nebo pohledům. Chování za běhu reprezentuje dynamickou stránku řešení problému. • Jak účastníci spolupracují, jak je organizována práce mezi nimi?
17
Příklad • Před popisem vzoru je třeba vzor identifikovat a abstrahovat od konkrétností. • Při návrhu konkrétní aplikace se objevují některé analogie mezi chováním vytvářených objektů (mají shodné stavy a přechody), jejich vazbami na okolní objekty (vystupují v podobných rolích).
18
Cesty jak odhalit NV (DP) • Postup, který zachycuje vztahy mezi skupinami rolí objektů. • Např. benzínová pumpa – objekty: – zákazník – role nakupujícího – benzínová pumpa – role prodejce – auto – role příjemce
• Abstrakcí od detailů dojdeme k návrhu obecného vzoru prodeje (orientujeme se na role).
19
Cesty jak odhalit NV (DP) • Pro prodej, kýmkoli realizovaný, je typické výskyt tří rolí: – prodejce – kupující – příjemce
• Zaznamenáním vzájemného vztahu a operací probíhajících mezi těmito třemi aktory, vzniká návrhový vzor. • Obecný vzor prodeje je potom možné aplikovat na jakýkoli prodej. 20
Formalizace popisu řešení • CRC card – Class Responsibility Collaborators • Class – třída, její název • Responsibilities – funkčnost, zodpovědnost • Collaborators – spolupracující třídy, komponenty • Jiná grafická notace než UML • UML – grafická notace 21
Seznam probíraných vzorů • • •
Singleton – jedináček Singleton ve společné oblasti (pool) Vrstvy (Layers)
22
Jedináček - Singleton • Problém: • definovat třídy tak, aby uživatel nemohl svobodně ovlivňovat počet jejich instancí – chceme např. aby se vytvořila pouze jedna instance dané třídy
• Kontext: • v mnoha aplikacích potřebujeme, aby vznikla pouze jedna instance (objekt) sdílený celou aplikací
23
Jedináček - Singleton • Řešení: • definovat konstruktor jako soukromý – private tím je zabezpečeno, že se ke konstruktoru dostaneme pouze prostřednictvím jiné metody, která bude veřejná • použít při deklaraci takové třídy modifikátor final, který zabezpečí, že třída již nemůže mít potomky (nedovolí deklaraci dalších podtříd)
24
Jedináček - Singleton • přímo v deklaraci třídy se vytvoří nová instance (objekt) s daným označením, které je přístupné např. prostřednictvím metody getReference(). • metoda pro získání odkazu na jediný objekt, jedináček se deklaruje jako statická, což umožňuje vytvářet instance s využitím názvu třídy
25
Grafické znázornění
26
public final class Ucet { private int cislo; private int stav; private static Ucet ucetSingleton; // tridni, staticka promenna private Ucet() { // vsechny konstruktory jsou private this(0, 0); }
Poznámky Účet – singleton
private Ucet(int cislo, int stav) { this.cislo = cislo; this.stav = stav; } // tovarni (factory) metoda pro ziskani instance uctu public static Ucet getInstance(){ if(ucetSingleton == null) ucetSingleton = new Ucet(1, 0); return ucetSingleton; } public void vlozeni (int castka) { stav = stav + castka; } public int vyber (int castka) stav = stav - castka; return stav; }
{
27
public String toString() { return String.format("Cislo uctu: %d stav uctu: %d",getCislo(), getStav()); }
Poznámky Účet – singleton
}
28
public class Osoba { private String jmeno; private int rokNarozeni; //pripojeni pres tridni promennou private static Ucet ucet = Ucet.getInstance(); // deklarace konstruktoru public Osoba() { this("neuvedeno", 0); } public Osoba(String jmeno, int rokNarozeni) { this.jmeno= jmeno; this.rokNarozeni = rokNarozeni; } public String toString() { String tx= String.format("\nJmeno: %s rok narozeni: %4d\nUcet: %s", getJmeno(), getRokNarozeni(), ucet.toString()); return tx; }
Poznámky Osoba – využívá účet 1. varianta třídní proměnná ucet odkazuje na singleton – jedináčka účtu
public Ucet getUcet(){ return ucet; } public void vlozeni(int castka){ ucet.vlozeni(castka); } public void vyber(int castka) { ucet.vyber(castka); }
29
public class OsobaX { private String jmeno; private int rokNarozeni; //private static Ucet ucet = Ucet.getInstance(); // deklarace konstruktoru public OsobaX() { this("neuvedeno", 0); } public OsobaX(String jmeno, int rokNarozeni) { this.jmeno= jmeno; this.rokNarozeni = rokNarozeni; } public String toString() { Ucet ucet = Ucet.getInstance(); String tx= String.format("\nJmeno: %s rok narozeni: %4d\nUcet: %s", getJmeno(), getRokNarozeni(), ucet.toString()); return tx; } public Ucet getUcet(){ Ucet ucet = Ucet.getInstance(); return ucet; } public void vlozeni(int castka){ Ucet ucet = Ucet.getInstance(); ucet.vlozeni(castka); } public void tiskUcet(){ Ucet ucet = Ucet.getInstance(); System.out.println("Ucet: "+ucet.toString()); }
Poznámky Osoba – využívá účet 2. varianta v každé metodě se musí deklarovat třídní proměnná ucet odkazující na singleton – jedináčka účtu
30
public class OsobaTest { public static void main(String[] args) { Osoba o1, o2, o3; OsobaX o11, o12; o1 = new Osoba("Adam", 1988); o2 = new Osoba("Alice", 1922); o3 = new Osoba("Iveta", 1977); o11 = new OsobaX("Silvestr", 1982); o12 = new OsobaX("Renata", 1975);
Poznámky OsobaTest
o1.vlozeni(300); o2.vyber(200); o12.vyber(50); o11.tisk(); o3.vlozeni(750); o12.vyber(400); o1.tisk(); if(o12.getUcet() == o1.getUcet()) System.out.println("Ucty jsou stejne"); else System.out.println("Ucty se lisi"); } }
31
Singleton • deklarace třídy „singletonu“ final • deklarace všech konstruktorů – private • deklarace třídní proměnné odkazující se na singleton • deklarace metody getInstance(), getReference(), getConnection() • singleton ve společné oblasti „pool“ např. pro připojení databáze – pouze stanovený počet instancí 32
public interface Connection { Object get(); void set(Object x); } public class ConnectionImplementation implements Connection { public Object get() { return null; } public void set(Object s) { ConnectionPool.checkConnection(this); } } public class EmptyPoolException extends Exception { public EmptyPoolException(String s){ super(s); } }
Poznámky
public class ConnectionPool { //A singleton private static PoolManager pool = new PoolManager(); //private static PoolManager pool; /* public static PoolManager getInstance(){ if(pool == null) pool = new PoolManager(); return pool; } // not used declaration - getConnection is the access method */ public static void addConnections(int number) { for(int i = 0; i < number; i++) pool.add(new ConnectionImplementation(), i+1); } public static Connection getConnection() throws EmptyPoolException { return (Connection)pool.get(); } public static void releaseConnection(Connection c) { pool.release(c); } public static void print() { pool.print(); }
Poznámky
Poznámky public static void checkConnection(Connection c){ pool.checkConnection(c); } }
public class PoolManager { private class PoolItem { //inner class boolean inUse = false; int ident; Object item; PoolItem(Object item, int i) { this.item = item; ident = i; } public String toString() { return String.format("ident: %d inUse: %s", ident, inUse ? "true" : "false"); } }// end inner class private ArrayList
items = new ArrayList (); public void add(Object item, int id) { items.add(new PoolItem(item, id)); }
Poznámky
Poznámky public Object get() throws EmptyPoolException { for(int i = 0; i < items.size(); i++) { PoolItem pitem = items.get(i); if(pitem.inUse == false) { pitem.inUse = true; return pitem.item; } } throw new EmptyPoolException(" No Free Connection "); // return null; // Delayed failure }
public void release(Object item) { for(int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem)items.get(i); if(item == pitem.item) { pitem.inUse = false; item = null; return; } } throw new RuntimeException(item + " not found"); } public void checkConnection(Object item){ for(int i = 0; i < items.size(); i++) { PoolItem pitem = (PoolItem)items.get(i); if(item == pitem.item && pitem.inUse == false) throw new RuntimeException(item + " out of function - released "); } } public void print() { for(int i = 0; i < items.size(); i++) { System.out.println(items.get(i).toString()); } } }
Poznámky
public class ConnectionPoolDemo { public static void main(String[] args) { ConnectionPool.addConnections(3); ConnectionPool.print(); Connection c = null; try { c = ConnectionPool.getConnection(); c.set(new Object()); c.get(); } catch (EmptyPoolException e) { //throw new RuntimeException(e); System.err.printf("EmptyPoolException %s\n", e); } ConnectionPool.print(); Connection c1 = null; try { c1 = ConnectionPool.getConnection(); c1.set(new Object()); c1.get(); } catch (EmptyPoolException e) { //throw new RuntimeException(e); System.err.printf("EmptyPoolException %s\n", e); }
Poznámky
ConnectionPool.print(); Connection c2 = null; try { c2 = ConnectionPool.getConnection(); c2.set(new Object()); c2.get(); } catch (EmptyPoolException e) { //throw new RuntimeException(e); System.err.printf("EmptyPoolException %s\n", e); } //ConnectionPool.releaseConnection(c); ConnectionPool.print(); Connection c3 = null; try { c3 = ConnectionPool.getConnection(); c3.set(new Object()); c3.get(); } catch (EmptyPoolException e) { //throw new RuntimeException(e); System.err.printf("EmptyPoolException %s\n", e); }
Poznámky
ConnectionPool.print(); ConnectionPool.releaseConnection(c2); c2.set(new Object()); c2.get(); ConnectionPool.print(); c = null; try { c = ConnectionPool.getConnection(); c.set(new Object()); c.get(); } catch (EmptyPoolException e) { //throw new RuntimeException(e); System.err.printf("EmptyPoolException %s\n", e); } ConnectionPool.releaseConnection(c); ConnectionPool.print(); } }
Poznámky
Vzor architektury – 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.
42
Vzor architektury – vrstvy (Layers) • Protokol specifikuje dohody v množství abstraktních úrovní, začínající od přenosu bitů až po nejvyšší úroveň aplikační logiky.
43
Jednoduché schema • 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í. FTP protokol FTP
FTP TCP protokol
TCP
TCP IP protokol
IP
Ethernet
IP Ethernet protokol
Ethernet
Fyzické propojení
44
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ý. 45
Problém • 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
46
Ř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.
47
Řešení • 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. Klient
Vrstva N
nejvyšší úroveň abstrakce
Vrstva N-1
Vrstva N-1
nejnižší úroveň abstrakce
48
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. 49
Scénáře použití 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.
50
Scénáře použití 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 nejvyšší nutné vrstvě. 51
Scénáře použití 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ů.
52
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. 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.
53
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.
54
Známé aplikace • Virtuální stroje. Příkladem může být Java Virtual Machine (JVM) definující formát binárního kódu. Protože je 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ů. 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. 55
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. • Zaměnitelnost. Daná implementovaná vrstva může být zaměněna sémanticky ekvivalentní implementací bez velkého úsilí. 56
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.
57