Návrhové vzory Design Patterns doc. Ing. František Huňka, CSc. Ostravská univerzita PřF
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 Stav – State Příkaz - Command
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í
Ucet
OsobaA OsobaC
OsobaB
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("\ String.format("\nJmeno: %s rok narozeni: %4d\ %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("\ String.format("\nJmeno: %s rok narozeni: %4d\ %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
Vzor Command - příkaz • Problém: • Standardní cesta pro vykonání metody je její vyvolání. • V některých situacích nejsme schopni řídit načasování (timing) kontextu, ve kterém by se měly metody volat. • Dalším problémem je flexibilní zpracování návratové hodnoty metody.
32
Vzor Command - příkaz • Kontext: • Klient, který využívá služeb (metod) objektu volá operaci, která vrací návratovou hodnotu, podle které se rozhodne o dalším pokračování programu. • Pro jednoduchost předpokládejme návrat celočíselné hodnoty např. 1 a 2 a podle jich se rozhodne jak dále. • Samozřejmě je třeba ještě ošetřit jinou návratovou hodnotu, která způsobí chybu. 33
Vzor Command - příkaz • Původní řešení je následující: switch (hodnota) { 1 : a.nejakaOperace( ); 2 : b.jinaOperace( ); } • Uvedené řešení je funkční, ale není flexibilní. Přidání další možné návratové hodnoty znamená přepsat (doplnit) uvedený kód. 34
Vzor Command - příkaz • Řešení: • Zaveďme rozhraní s operací execute( ). • Pro každé zpracování návratové hodnoty zavedeme další třídu pod tímto rozhraním a v této třídě implementujeme metodu execute( ). • Ta pak provede patřičné operace např. a.nejakaOperace( ).
35
Vzor Command - příkaz • Řešení: • Požadavek (metodu) zapouzdříme do podoby objektu (objektové proměnné), takže můžeme s požadavkem pracovat jako s každou jinou proměnnou, což vede k parametrizaci požadavků. (dynamické tvorbě seznamu požadavků, dosazení požadavku za jiný požadavek apod.)
36
Command – diagram tříd UML
37
interface Command { void execute(); }
Poznámky
38
public class TridaA { private double hodnota; public TridaA(double hodnota) { this.hodnota = hodnota; } public double nejakaOperace() { System.out.println("nejakaOperace - TridaA"); return Math.sqrt(hodnota); }
Poznámky
}
39
public class TridaA { private double hodnota; public TridaA(double hodnota) { this.hodnota = hodnota; } public double nejakaOperace() { System.out.println("nejakaOperace - TridaA"); return Math.sqrt(hodnota); }
Poznámky
}
40
public class CalculationA implements Command{ private TridaA tridaA; public CalculationA(TridaA tridaA) { this.tridaA = tridaA; } public void execute() { //implementace metody System.out.println(tridaA.nejakaOperace()); }
Poznámky
}
41
public class CalculationB implements Command { private TridaB tridaB; public CalculationB(TridaB tridaB) { this.tridaB = tridaB; } public void execute() { tridaB.jinaOperace(); System.out.printf("%.2f", tridaB.jinaOperace()); }
Poznámky
}
42
public class CommandPattern {
Poznámky
public static void main(String[] args) { TridaA tridaA = new TridaA(22); TridaB tridaB = new TridaB(84); CalculationA calculA = new CalculationA(tridaA); CalculationB calculB = new CalculationB(tridaB); calculA.execute(); calculB.execute(); } }
43
Vzor Command - příkaz • Příkazy vzoru Command se dají uložit do objektu třídy Register kvalifikovaného na objekt rozhraní Command • To pak vede k tomu, že se dá vytvořit dynamický seznam požadavků, které se postupně provádějí.
44
public class RegisterC { private Command pole[]; private int top;
Poznámky
// konstruktor public RegisterC(int pocet) { top = -1; pole = new Command[pocet]; Command[pocet]; } public void vlozit(Command vlozit(Command prvek) { if ((top + 1) < pole.length) { top += 1; pole[top] = prvek; } else System.out.println("Registr je obsazeny"); } public Command getPrvek(int i) { Command prvek=null; if (i>=0 && i< pole.length) prvek = pole[i]; else System.out.printf("%s %d %s\ %s\n","Index",i,"mimo rozsah"); return prvek; } . . . }
45
public class CommandPattern{
Poznámky
public static void main(String args[]) { // new Command1().execute(25); //funkční // new Command2().execute(49); Command c = new Command1(); c.execute(6); c = new Command2(); c.execute(6); RegisterC rc = new RegisterC(6); rc.vlozit(c); rc.vlozit(c); c = new Command1(); rc.vlozit(c); rc.vlozit(c); System.out.println("Vypis registru"); for (int i =0; i<=rc.getTop(); i++){ rc.getPrvek(i).execute(i+3 rc.getPrvek(i).execute(i+3); i+3); } } }
46
nejakaOperace - TridaA 36.0 jinaOperace - TridaB 2,45
Poznámky
Vypis registru jinaOperace - TridaB 1,73 jinaOperace - TridaB 2,00 nejakaOperace - TridaA 25.0 nejakaOperace - TridaA 36.0
47
Jednodušší varianta vzoru Command • Přímo třídy Command1 a Command2 implementují požadované metody.
48
interface Command { void execute(); }
Poznámky
class Hello implements Command { public void execute() { System.out.println("Hello "); } } class World implements Command { public void execute() { System.out.println("World! "); } } class IAm implements Command { public void execute() { System.out.println("I'm the command pattern!"); } }
49
// An object that holds commands: class Macro { private List
commands = new ArrayList (); public void add(Command c) { commands.add(c); } public void run() { Iterator it = commands.iterator(); while(it.hasNext()) ((Command)it.next()).execute(); } }
Poznámky
50
public class CommandPattern{ public static void main(String args[]) {
Poznámky
Macro macro = new Macro(); macro.add(new Hello()); macro.add(new World()); macro.add(new IAm()); macro.run(); // další možnosti new IAm().execute(); new Hello().execute(); } }
51
Vzor Command • 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á. 52
public interface Command { public void execute(); }
Poznámky Příklad – garáž, vrata: up, down, stop světlo: on, off,
53
public class GarageDoor {
Poznámky
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"); } }
54
public class GarageDoorOpenCommand implements Command { GarageDoor garageDoor;
Poznámky
public GarageDoorOpenCommand(GarageDoor garageDoor) { this.garageDoor = garageDoor; } public void execute() { garageDoor.up(); } }
55
public class Light {
Poznámky
public Light() { } public void on() { System.out.println("Light is on"); } public void off() { System.out.println("Light is off"); } }
56
public class LightOffCommand implements Command { Light light;
Poznámky
public LightOffCommand(Light light) { this.light = light; } public void execute() { light.off(); } }
57
public class LightOnCommand implements Command { Light light;
Poznámky
public LightOnCommand(Light light) { this.light = light; } public void execute() { light.on(); } }
58
public class SimpleRemoteControl { Command slot;
Poznámky
public SimpleRemoteControl() {} public void setCommand(Command command) { slot = command; } public void buttonWasPressed() { slot.execute(); } }
59
public class RemoteControlTest { public static void main(String[] args) { SimpleRemoteControl remote = new SimpleRemoteControl(); Light light = new Light(); GarageDoor garageDoor = new GarageDoor(); LightOnCommand lightOn = new LightOnCommand(light); GarageDoorOpenCommand garageOpen = new GarageDoorOpenCommand(garageDoor); LightOffCommand lightOff = new LightOffCommand(light);
Poznámky
remote.setCommand(lightOn); remote.buttonWasPressed(); remote.setCommand(garageOpen); remote.buttonWasPressed(); remote.setCommand(lightOff); remote.buttonWasPressed(); } }
60