SW_11 • Strategie - Strategy • Šablona – metoda template
1
Strategie • Vzor strategie definuje množinu algoritmů, která každý zapouzdřuje a umožňuje je zaměňovat. • Strategie dovoluje, aby se algoritmus měnit (pozměnil) nezávisle na klientovi, který ho používá. • Záměrem vzoru strategie je zapouzdřit alternativní přístupy nebo strategie v samostatných třídách, kde každá implementuje danou operaci. 2
Strategy • Kontext: • Simulační hra s kachnami na „rybníce“. • Simulace plavání (swim) a kvákání (quack). • Společné metody v nadtřídě. • Metoda display() je abstraktní.
Duck quack() swim() display() fly() // other duck-like methods
MallardDuck display() { // looks like a mallard }
RedheadDuck display() { // looks like a readhead }
Another types of ducks inherit from the Duck class
3
Strategy • Inovace: plavající kachny. • Zařazením metody fly() do nadtřídy – všechny kachny v podtřídách mají metody fly(). • Ale ne všechny kachny v podtřídách umí létat.
Duck quack() swim() display() fly() // other duck-like methods
RubberDuck MallardDuck display() { // looks like a mallard }
RedheadDuck display() { // looks like a readhead }
quack() { // overridden to Squeak } display() { // looks like a rubberduck }
4
Strategy • Ve třídě RubberDuck – nutno zastínit (override) metodu fly() stejně jako ostatní nerelevantní metody. • Třída DecoyDuck – kachna vábnička – je také třeba zastínit metodu fly().
RubberDuck quack() { //squeak } display() { // rubber duck } fly() { // override to do nothing }
DecoyDuck quack() { // override to do nothing } display() { // decoy duck } fly() { // override to do nothing }
5
Strategy • Co použít rozhraní? • Rozhraní Flyable – mají pouze kachny které létají, Quackable pouze kvákající kachny.
6
Strategy • Ne všechny podtřídy by měly mít chování „flyable“ nebo „quackable“. • Implementací uvedených rozhraní se řeší jen část problému. • RubberDuck – fly() ?? • Místo pro zavedení vzoru. • Jedna základní pravda při tvorbě programového vybavení : ZMĚNA 7
Strategy - zaměření se na problém • Využití dědičnosti nesplňovalo všechny požadavky – chování kachny se mění v podtřídách – není vhodné, aby to ostatní podtřídy z nadtřídy dědily. • Rozhraní Flyable a Quackable – nadějné, ale pouze kachny, které skutečné létají by měly implementovat Flyable. • Navíc rozhraní nemá žádný kód => nutnost přepisování kódu v podtřídách, potenciální zdroj chyb. 8
Návrhový princip (zásada) • Identifikujte aspekty aplikace, které se mění a oddělte je od těch, které zůstávají stejné. • Aspekty aplikace, které se mění s každým dalším požadavkem (při přidání dalšího typu kachny). • Vezměte části, které se mění a zapouzdřete je, takže je později můžete měnit nebo rozšiřovat bez ovlivňování neměnných částí. • Nechte danou část systému měnit nezávisle na dalších částech systému. 9
Separace toho co se mění od toho co zůstává stejné • Výsledek: – méně nechtěných závislostí při změnách kódu a více pružnosti celého systému.
• Metody fly() a quack() jsou části, které se s dalšími požadavky na nadtřídu Duck mění. • Oddělme toto chování od třídy Duck a vytvořme množinu tříd, reprezentující každé chování. • Implementace fly() a quack() jsou v řadě tříd.
10
Návrhový princip (zásada) • Chceme zavést novou podtřídu MallardDuck a inicializovat se specifickým typem létání. • Programujte k rozhraní ne k implementaci. • Rozhraní Flyable bude implementovat různé druhy létání. • Třídy implementují konkrétní typ např. létání a jsou nezávislé – liší se od předchozího.
«interface» FlyBehavior fly()
FlyWithWings fly() { // implements duck flying }
FlyNoWay fly() { // do nothing - can’t fly }
11
Návrhový princip (zásada) • Programováním proti rozhraní umožňuje si pak vybrat konkrétní typ létání – ne ho implementovat. • Programování proti implementaci:
Animal makeSound()
Dog d = new Dog(); d.bark();
• Programování proti rozhraní: Animal animal = new Dog(); animal.makeSound();
Dog
Cat
makeSound() { bark(); } bark() { // bark sound }
makeSound() { meow(); } meow() { // meow sound }
12
Implementace chování kachny
13
Integrace chování kachny
• Klíčové v chování kachny – delegování jejího chování – létání, kvákání místo deklarování létání a kvákání uvnitř nadtřídy Duck. • flyBehavior, quackBehavior – datové atributy odkazující se na uvedená rozhraní; mohou se dále polymorficky měnit na podtypy konkrétních rozhraní. 14
Integrace chování kachny 1. Jsou přidány dva datové atributy flyBehavior, quackBehavior, jako typy rozhraní. 2. Konkrétní implementace kvákání: public class Duck { QuackBehavior quackBehavior; // dalsi kod public void performQuack() { quackBehavior.quack(); } }
15
Integrace chování kachny 3. Jak jsou nastaveny datové atributy flyBehavior, quackBehavior public class MallardDuck extends Duck { public MallardDuck() { quackBehavior = new Quack(); flyBehavior = new FlyWithWings(); } public void display() { System.out.println(“I am a real Mallard duck”); } } 16
Integrace chování kachny • Zásada – neprogramovat proti implementaci. • V konstruktoru jsme vytvořili novou instanci konkrétní třídy Quack. • Zatímco v konstruktoru nastavujeme chování na konkrétní třídu (FlyWithWings, Quack), za běhu programu jsme schopni toto snadno změnit. • Změnu umožňuje polymorfismus.
17
public abstract class Duck { FlyBehavior flyBehavior; QuackBehavior quackBehavior;
Testování kódu public Duck() { } abstract void display(); public void performFly() { flyBehavior.fly(); } public void performQuack() { quackBehavior.quack(); } public void swim() { System.out.println("All ducks float, even decoys!"); } }
18
public interface FlyBehavior { public void fly(); } public class FlyWithWings implements FlyBehavior { public void fly() { System.out.println("I'm flying!!"); } }
public class FlyNoWay implements FlyBehavior { public void fly() { System.out.println("I can't fly"); } }
public interface QuackBehavior { public void quack(); }
public class Quack implements QuackBehavior { public void quack() { System.out.println("Quack"); } }
public class MuteQuack implements QuackBehavior { public void quack() { System.out.println("<< Silence >>"); } }
public class Squeak implements QuackBehavior { public void quack() { System.out.println("Squeak"); } }
public class MiniDuckSimulator { public static void main(String[] args) { MallardDuck mallard = new MallardDuck(); RubberDuck rubberDuckie = new RubberDuck(); DecoyDuck decoy = new DecoyDuck(); ModelDuck
model = new ModelDuck();
mallard.performQuack(); rubberDuckie.performQuack(); decoy.performQuack(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly(); } } java MiniDuckSimulator Quack I’m flying!!
Přidáme dvě nové metody do třídy Duck: public void setFlyBehavior (FlyBehavior fb) { flyBehavior = fb; } public void setQuackBehavior(QuackBehavior qb) { quackBehavior = qb; } Vytvoříme novou třídu DuckType public class ModelDuck extends Duck { public ModelDuck() { flyBehavior = new FlyNoWay(); quackBehavior = new Quack(); } public void display() { System.out.println("I'm a model duck"); } } Vytvoříme novou třídu pro chování: FlyRocketPowered public class FlyRocketPowered implements FlyBehavior { public void fly() { System.out.println("I'm flying with a rocket"); } }
Nastavení chování dynamicky Třída Duck
public class MiniDuckSimulator1 { public static void main(String[] args) { Duck mallard = new MallardDuck(); mallard.performQuack(); mallard.performFly(); Duck model = new ModelDuck(); model.performFly(); model.setFlyBehavior(new FlyRocketPowered()); model.performFly(); } } java MiniDuckSimulator1 Quack I’m flying!! I can’t can’t fly I’m flying with a rocket
Změníme testovací třídu
Velký obrázek zapouzdřeného chování
24
Kompozice HAS-A může být lepší než hierarchie IS-A • Relace HAS-A zajímavá v tom, že každá kachna má FlyBehavior a QuackBehavior na něž deleguje flying a quacking (létání a kvákání). • Místo dědění, kachny získávaní své chování tím, že jsou složeny s pravým objektem jejich chování. • Upřednostňujte skládání před dědičností.
25
Kompozice & dědičnost • Vytvoření systému používající skládání (kompozice) dává více flexibility. • Nejen že dovoluje zapouzdřit rodinu algoritmů do jejich vlastní množiny tříd, ale také dovoluje měnit chování za běhu (run time).
26
Vzor Strategie • Další příklad: • Vzor Strategie (Strategy) definuje množinu (rodinu) algoritmů, zapouzdřuje každý z nich a umožňuje jejich zaměnitelnost. • Strategie dovoluje algoritmu měnit se nezávisle na klientech, kteří je používají.
27
interface FindMinima { // Line is a sequence of points: double[] algorithm(double[] line); } // ruzne strategie: class LeastSquares implements FindMinima { public double[] algorithm(double[] line) { return new double[] { 1.1, 2.2 }; // Dummy } } class NewtonsMethod implements FindMinima { public double[] algorithm(double[] line) { return new double[] { 3.3, 4.4 }; // Dummy } } class Bisection implements FindMinima { public double[] algorithm(double[] line) { return new double[] { 5.5, 6.6 }; // Dummy } } class ConjugateGradient implements FindMinima { public double[] algorithm(double[] line) { return new double[] { 3.3, 4.4 }; // Dummy } }
// The "Context" controls the strategy: 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; } }
programováni proti rozhraní
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(); } }
Převádí pole na textovou reprezentaci
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))); } }
[1.1, 2.2] [5.5, 6.6]
Šablona - Template • Kontext: • Definice metod, které mají společný obecný základ, ale liší se v implementaci konkrétních kroků. • Problém: • Při definování metody bychom rádi definovali pouze základní obrysy (návrh) algoritmu a ponechali možnost rozdílné implementace jistých kroků.
32
Šablona • Řešení: • Zavedení vzoru Šablona (Template method), jejímž cílem je implementovat daný algoritmus v metodě, přičemž odkládá definování některých kroků algoritmu tak, že je jiné třídy mohou redefinovat (dodefinovat). • Nejdříve uvedeme příklad objasňující vzor šablona na příkladu přípravy kávy a čaje. Dále je uveden klasický příklad třídění. 33
public 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 brewCoffeeGrinds() { 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");
Metoda prepareRecipe() – obsahuje celý postup přípravy kávy. Dílčí metody jsou uvedeny dále. Metoda prepareRecipe() je pro třídu Tea podobná, má však dvě různé metody.
} }
34
public class Tea { final void prepareRecipe() { boilWater(); steepTeaBag(); // vyluhování čajového sáčku pourInCup(); // přidání citronu addLemon(); } ...
35
Šablona • Z kódu vidíme, že dvě metody jsou stejné jako ve třídě Coffee a dvě jsou různé.
36
Šablona • 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.
37
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řidání dochucovadel
void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("Pouring into cup"); } }
38
public class Coffee extends CaffeineBeverage { public void brew() { System.out.println("Dripping Coffee through filter"); } public void addCondiments() { System.out.println("Adding Sugar and Milk"); } }
39
Definice vzoru šablona • 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ů. • 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. 40
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() { } }
41
Šablona • 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 konkrétní metoda a nemusí dělat nic. • Pomáhá při nastavování defaultních hodnot.
42
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; } }
43
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; } }
44
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.
}
45
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.
46
Klasický případ 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ů.
47
Šablona třídění • 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)
48
Šablona třídění • Naproti tomu třída ArrayList poskytuje instanční metodu sort(), která třídí příjemce této zprávy. ArrayList al = arrayList.sort()
49
Šablona třídění • 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.
50
Šablona třídění • 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.
51
Šablona třídění • 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).
52
Šablona třídění • 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)
53
public class OsobaJmeno implements Comparable
{ private String jmeno, prijmeni; public OsobaJmeno(String jmeno, String prijmeni){ this.jmeno = jmeno; this.prijmeni = prijmeni; } 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)); } }
54
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)); } }
55
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 jmena = Arrays.asList(poleJmen); Collections.sort(jmena); System.out.println(jmena); } }
Využití třídění
56
public interface Comparator { int compare(T o1, T o2); boolean equals(Object o); }
Další porovnávání objektů je možné dělat s využitím rozhraní Comparator, které deklaruje dvě metody:
57
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;
Třída TimeComparator implementuje rozhraní Comparator k porovnání dvou objektů třídy Time
// 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í } }
58
// 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( list.add( list.add( list.add( list.add(
new new new new new
Time2( 6, 24, 34 ) Time2( 18, 14, 58 ) Time2( 6, 05, 34 ) Time2( 12, 14, 58 ) 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() );
59
// 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(); } }
60
Shrnutí • 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.
61