Beteendemässiga designmönster
Beteendemässiga designmönster
Beteendemässiga designmönster
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
<strong>Beteendemässiga</strong><br />
<strong>designmönster</strong><br />
Chain of Responsibility<br />
Command<br />
Interpreter<br />
Iterator<br />
Mediator<br />
Memento<br />
Observer<br />
State<br />
Strategy<br />
Template Method<br />
Visitor
Problem – olika kontroller av<br />
förfrågningar<br />
● Beroende på ett anrops parametrar, vill vi göra<br />
olika slags kontroller<br />
– T.ex. Webbservern anropas enligt autenticering,<br />
efter vilken sida som frågats, osv.<br />
● Klumpigt att ha alla kontrolleringar på ett och<br />
samma ställe – de har inget med varann att<br />
göra<br />
● Om vi har dem på flera ställen, ska klienten inte<br />
behöva veta om dom alla
Chain of Responsibility
I praktiken
Chain of Responsibility<br />
● Anropet skickas iväg till olika hanterare i given<br />
ordning.<br />
● Vi har tre alternativ:<br />
– Den första som sköter anropet ger svar<br />
– Vi kan skicka förfrågan vidare till nästa<br />
hanterare<br />
– Eller så hindrar vi förfrågan från att gå vidare<br />
● “Hindrandet” brukar också skötas så att vi ger<br />
ett visst svar, t.ex. ett felmeddelande
class Handler1 implements HandlerInterface {<br />
void handleRequest(Request req, Response rsp, Chain chain) {<br />
...<br />
chain.nextRequest();<br />
}<br />
}<br />
class Handler2 implements HandlerInterface {<br />
void handleRequest(Request req, Response rsp, Chain chain) {<br />
response.XXX; // Fill in the response<br />
chain.giveResponse()<br />
}<br />
}<br />
class Handler3 implements HandlerInterface {<br />
void handleRequest(Request req, Response rsp, Chain chain) {<br />
chain.stopResponse();<br />
}<br />
}
Chain of Responsiblity - diverse<br />
● Frigör sändaren av meddelandet/anropet från<br />
mottagaren<br />
● Gör objektet enklare<br />
● Möjliggör dynamisk ändring av kedjan<br />
● Exekveringen är inte garanterad; tänk om ingen<br />
i kedjan sköter om förfrågan<br />
● Ev. svårt att inse i vilken ordning de olika<br />
hanterarna ska vara<br />
● Ev. svårt att debugga<br />
– Nyttigt dock att kedjan syns från början till<br />
nuvarande position i tracebacken...
Application<br />
Chain of Responsibilityprocessen/objekten<br />
Chain<br />
nextHandler<br />
nextHandler<br />
Hanterare1 Hanterare2 Hanterare3
Problem - undo/redo<br />
● Flera applikationer har nytta av undo/redomöjligheter<br />
– T.ex. ett textbehandlingsprogram
Command
MoveShapeCommand<br />
● Vi har en klass MoveShapeCommand som<br />
– Kan flytta ett objekt från sin gamla plats till den<br />
nya givna platsen<br />
– Kan återställa detta (undo)<br />
– Kan om-exekvera kommandot (redo)<br />
● Konstruktorns uppgift (eller ev. skilda metoder)<br />
är att etablera informationen som behövs för att<br />
exekvera ovanstående
Första försöket<br />
class MoveShapeCommand implements<br />
Command {<br />
Shape s;<br />
}<br />
int x;<br />
int y;<br />
public MoveShapeCommand(<br />
Shape ss,<br />
int xx,<br />
int yy) {<br />
s = ss;<br />
x = xx;<br />
y = yy;<br />
}<br />
void execute() {<br />
int oldx = s.getX();<br />
int oldy = s.getY();<br />
s.setX(x);<br />
s.setY(y);<br />
x = oldx;<br />
y = oldy;<br />
}<br />
}<br />
void undo() {<br />
int oldx = s.getX();<br />
int oldy = s.getY();<br />
s.setX(x);<br />
s.setY(y);<br />
x = oldx;<br />
y = oldy;<br />
}<br />
void redo() {<br />
int oldx = s.getX();<br />
int oldy = s.getY();<br />
s.setX(x);<br />
s.setY(y);<br />
x = oldx;<br />
y = oldy;<br />
}
class MoveShapeCommand implements Command {<br />
Shape s;<br />
int x;<br />
int y;<br />
public MoveShapeCommand(Shape ss, int xx, int yy) {<br />
s = ss;<br />
x = xx;<br />
y = yy;<br />
}<br />
void undo() {<br />
}<br />
int oldx = s.getX();<br />
int oldy = s.getY();<br />
s.setX(x);<br />
s.setY(y);<br />
x = oldx;<br />
y = oldy;<br />
}<br />
void redo() {<br />
undo();<br />
}<br />
● Många kommandon kan<br />
implementeras så att<br />
execute() är samma som<br />
redo()<br />
● Många kommandon kan<br />
implementeras så att redo()<br />
är samma som undo()
● Man kan även<br />
Command - diverse<br />
– Exekvera kommandon senare<br />
– Logga kommandon<br />
– Serialisera kommandon<br />
– Göra t.ex. Playback<br />
● Nyttig även utan undo!<br />
● Detalj: Undo/Redo kan ändå implementeras på<br />
olika sätt, jämför t.ex. Word och Emacs
Command Queue med en Thread<br />
Pool<br />
● Många klienter vill få sina kommandon uträttade<br />
● P.g.a. kapasitetsproblem vill man inte exekvera<br />
kommandon genast när de kommer<br />
● Kommandon lagras i kö<br />
● En skild mängd med threads plockar ut<br />
kommandon och exekverar dem<br />
Client 1<br />
Client 2<br />
Work4<br />
Work3<br />
Work2<br />
Work1<br />
Thread 1<br />
Thread 2
● Vi kan länka<br />
flera<br />
kommandon in<br />
i ett nytt<br />
kommando<br />
● Observera att<br />
redo() kör<br />
kommandona<br />
baklänges<br />
Macro Command<br />
class MacroCommand implements Command {<br />
Command[ ] cmds;<br />
public MacroCommand(Command[ ] c) {<br />
cmds = c;<br />
}<br />
public addCommand(Command c) {<br />
...<br />
}<br />
void undo() {<br />
for (int i = cmds.size() - 1; i >= 0; --i) {<br />
Command c = cmds[i];<br />
c.undo();<br />
}<br />
}<br />
void redo() {<br />
for(Command c: cmds) {<br />
c.redo();<br />
}<br />
}<br />
}
Interpreter<br />
● Vi behöver ett enkelt skriptspråk i vårt program<br />
● Behövs:<br />
– Språk (grammatik)<br />
– Lexer<br />
– Parser<br />
– Interpreterare
● Språket brukar ges i Backus-Naur Form (BNF)<br />
● Exempelvis ett språk för enkla beräkningar:<br />
– E -> Terminal | Operation<br />
– Operation -> Plus | Minus<br />
– Plus -> E '+' E<br />
– Minus -> E '-' E<br />
– Terminal -> [0-9]+<br />
● (Vi funderar inte här över operatorers<br />
precedenser)
Syntaxträd<br />
● Syntaxträdet för (1023 + x) - 500
● interpret()<br />
Interpreter-mönstret
import java.util.*;<br />
interface Expression {<br />
public int interpret(HashMap variables);<br />
}<br />
class Number implements Expression {<br />
private int number;<br />
public Number(int number) { this.number = number; }<br />
public int interpret(HashMap variables) { return number; }<br />
}<br />
class Plus implements Expression {<br />
Expression leftOperand;<br />
Expression rightOperand;<br />
public Plus(Expression left, Expression right) {<br />
leftOperand = left;<br />
rightOperand = right;<br />
}<br />
}<br />
public int interpret(HashMap variables) {<br />
return leftOperand.interpret(variables) + rightOperand.interpret(variables);<br />
}<br />
class Minus implements Expression {<br />
Expression leftOperand;<br />
Expression rightOperand;<br />
public Minus(Expression left, Expression right) {<br />
leftOperand = left;<br />
rightOperand = right;<br />
}<br />
}<br />
public int interpret(HashMap variables) {<br />
return leftOperand.interpret(variables) - rightOperand.interpret(variables);<br />
}<br />
class Variable implements Expression {<br />
private String name;<br />
public Variable(String name) { this.name = name; }<br />
public int interpret(HashMap variables) {<br />
return variables.get(name);<br />
}<br />
}<br />
Exempel
Evaluering<br />
public class InterpreterExample {<br />
public static void main(String[] args) {<br />
String expression = "(1023 + x) - 500";<br />
Evaluator sentence = new Evaluator(expression);<br />
HashMap variables = new HashMap();<br />
variables.put("x", 1);<br />
int result = sentence.evaluate(variables);<br />
System.out.println(result);<br />
}<br />
}
Men!<br />
● Det finns betydligt bättre verktyg för tillverkning<br />
av parsers och interpreterare<br />
– Yacc<br />
– Lexx<br />
– Bison<br />
● Bäst att gå en kurs i kompilatorteknik
Gammalt problem: vi vill anropa en<br />
metod i alla lövnoder<br />
● Att iterera igenom trädstrukturen och anropa<br />
alla lövnoders operation1() var jobbigt<br />
● Måste vi copy-paste koden för att anropa<br />
operation2() ?
En alternativ lösning<br />
● Skild iteratorklass som bara returnerar lövnoder<br />
– Lite jobbig att skriva, men behöver bara göras en<br />
gång
● Användning:<br />
● Eller (i Java 5):<br />
void m(Component c) {<br />
Iterator i = c.createLeafIterator();<br />
while (i.hasNext()) {<br />
Leaf lf = i.next();<br />
lf.operation1();<br />
}<br />
}<br />
void m(Component c) {<br />
for (Leaf lf : c.createLeafIterator() ) {<br />
lf.operation1();<br />
}<br />
}
Iterator<br />
● Mönstret ger ett gränssnitt för att iterera igenom<br />
valfria samlingar<br />
● Vill någon klass delta, kan man bara göra en<br />
lämplig subklass till Iterator
Iterator - diverse<br />
● Iteratorn behöver inte gå igenom en<br />
existerande samling<br />
– En generator genererar samlingen on-the-fly från<br />
t.ex. formler<br />
– Kan kombinera samlingar, filtrera element från dem<br />
o.s.v.<br />
● Själva iteratorn och klassen som har samlingen<br />
brukar vara lite i symbios<br />
– Vad händer om samlingen modifieras medan någon<br />
annan itererar igenom samlingen?<br />
– ( Problemet är att klienten bestämmer hur snabbt<br />
iteratorn används )
Och i ett bättre språk än Java...<br />
● “Javafierad” syntax...<br />
● Användning:<br />
void m(Component c) {<br />
c.callLeafs(lambda(Leaf x) {<br />
x.operation1();<br />
});<br />
}<br />
class Composite {<br />
void callLeafs(Function f) {<br />
for (Component c: children) {<br />
c.callLeafs(f);<br />
}<br />
}<br />
}<br />
class Leaf {<br />
void callLeafs(Function f) {<br />
f(this);<br />
}<br />
}<br />
Du behöver<br />
inte kunna<br />
denna<br />
sida
Vem bestämmer?<br />
● Flera samarbetande komponenter kan lätt bli<br />
råddigt<br />
– Kan vara n*(n-1)/2 parrelationer!<br />
– Vem uppdaterar vem?<br />
– Eventuella nya objekt ska lätt kunna integreras
Mediator<br />
● Alla komponenter uppdaterar sig bara till ett<br />
ställe, som sedan uppdaterar de övriga<br />
● Lättare att skriva och förstå komponenterna
Eller mer generellt...
Mediator<br />
● Ökar återanvändbarheten bland<br />
komponenterna en aning<br />
● Vi kan införa gemensamma regler för<br />
kommunikation<br />
– Minskar på olika kommunikationssätt => lättare att<br />
förstå<br />
● Mönstrets centrala objekt kan bli krångligt<br />
● Jämför med Facade: fasaden är till för att<br />
klienten ska få ett enkelt och bättre gränssnitt,<br />
Mediator är till för att de interna komponenterna<br />
ska kunna samarbeta lättare
Rollback till ett tidigare tillstånd<br />
● Objekt kan behöva “snapshottas” för att kunna<br />
spara deras tillstånd<br />
● T.ex följande kan vara nyttiga att kunna<br />
återställas till originaltillståndet<br />
– Slumptalsgeneratorer<br />
– Tillståndsmaskiner
Spara / ladda data<br />
● De flesta applikationer vill spara och ladda data<br />
– Spel, kontorsprogram, osv.<br />
● Automatisk serialisering är en dålig idé – tycker<br />
jag<br />
– Ev. beroende på just den versionen av<br />
programmeringsomgivningen<br />
– Ev. svårt att vara kompatibel om dataformatet<br />
ändrar (dvs fälten ändrar i klassen)<br />
– Andra program, skriva i andra språk, kan ha svårt<br />
för att kommunicera i samma format
Memento<br />
● Märk att klienten inte har tillgång till datat<br />
● Snapshotten över datat sparas i en skild klass!<br />
● Kan vara tidskrävande att spara eller ladda<br />
datat
Problem - ändringar i data leder till<br />
fler ändringar<br />
● När något data ändrar, behöver vi ofta<br />
uppdatera något annat data också<br />
– Exempel finns i överflöd: vilket system som helst<br />
har nytta av att en ändring “triggar” en annan<br />
ändring<br />
● Problemet är att vi inte vet vad som behöver<br />
uppdateras<br />
class Player {<br />
int health;<br />
void damage(Weapon w, int points) {<br />
health -= points;<br />
// Tell the rest<br />
GUI.tellOtherPlayersIGotHit();<br />
GUI.tellMyScreenDrawingRoutineIGotHit();<br />
GUI.tellMyScreenThatILostHealth();<br />
...<br />
// Did we forget anybody?<br />
}<br />
}
● Ett system består av<br />
● Subjekt<br />
● Observerare<br />
Generellt<br />
● Behöver inte vara skilda objekt, ett subjekt kan<br />
även observera någon
Är du intresserad?<br />
● Egentligen är subjektet inte intresserad av att<br />
berätta om sin ändring åt de observerare den<br />
känner till<br />
– Beroendet är dubbelt; både subjekt och observerare<br />
vet om varandra<br />
– En ny observerare kräver en ändring i subjektet<br />
● Subjektet borde berätta åt alla de observerare<br />
som är intresserade av subjektet<br />
– Men observerare bör berätta att de är intresserade
● Subject-klassen vet om sina observerare<br />
● Men S1 och S2 behöver inte veta
Kodexempel<br />
class Subject {<br />
private ArrayList observers;<br />
protected void notify() {<br />
for (Observer o: observers) {<br />
o.update(this);<br />
}<br />
}<br />
public void attach(Observer o) {<br />
observers.add(o);<br />
}<br />
public void detach(Observer o) {<br />
observers.remove(o);<br />
}<br />
}<br />
class S1 extends Subject {<br />
public void doSomething() {<br />
notify();<br />
}<br />
}<br />
● Användning:<br />
S1 s = new S1();<br />
...<br />
...<br />
...<br />
Observer o1 = new O1();<br />
Observer o2 = new O2();<br />
s.attach(o1);<br />
s.attach(o2);<br />
...<br />
s.doSomething();
1<br />
Kodexempel<br />
class Subject {<br />
private ArrayList observers;<br />
protected void notify(...) {<br />
for (Observer o: observers) {<br />
o.update(this, ...);<br />
}<br />
}<br />
2 3<br />
}<br />
public void attach(Observer o) {<br />
observers.add(o);<br />
}<br />
public void detach(Observer o) {<br />
observers.remove(o);<br />
}<br />
● 1) notify() kan bara<br />
anropas av<br />
subklasserna<br />
● 2) observerarna får<br />
veta vilket subjekt<br />
som ändras<br />
● 3) eventuella extra<br />
parametrar kan ges
Varianter<br />
● Inget data skickas med notify() versus data kan<br />
skickas<br />
– Kan eventuellt använda olika notify-metoder<br />
● Observeraren får inte veta vilket subjekt som<br />
ändrats versus en referens till subjektet skickas<br />
med<br />
– För varje observerare finns högst ett subjekt<br />
● Ett subjekt kan ha flera s.k. signaler<br />
– T.ex. QT-biblioteket<br />
– Brukar kallas för signal/slot-mekanismen
● Andra namn:<br />
– Model/View<br />
– Publish/Subscribe<br />
– Document/View<br />
– Observer<br />
Subject/Observer<br />
● Kan ge prestandaproblem med många subjekt/<br />
observerare<br />
● Kan åka i oändlig loop om subjekt S anropar<br />
observeraren, som själv är ett subjekt som<br />
observeras av S...
C# delegate<br />
Du behöver<br />
inte kunna<br />
denna<br />
sida<br />
● C# har subject-observer mönstret inbyggt i form<br />
av delegater
Automater<br />
● En del system fungerar<br />
enligt klara regler<br />
– Coca Cola-maskin,<br />
datorprotokoll<br />
– Regelantalet kan vara<br />
stort & komplicerat<br />
● Systemen fungerar<br />
olika vid olika<br />
tidpunkter!<br />
● Modellering av dessa<br />
görs bäst med<br />
tillståndsmaskiner<br />
(eng. state machines)
Tillståndsmaskiner<br />
● De facto standard: UML 2.0 State machines<br />
– Maskinerna kan även presenteras i t.ex. tabellform
Vad består en tillståndsmaskin av?<br />
● Vakter (eng. guards)<br />
● Händelser (eng. events)<br />
● Tillstånd (eng. states)<br />
● Aktioner (eng. actions)<br />
● Eventuella tillägg<br />
– enter/exit, hierarkier, synkronisering, fork/join,<br />
historia
Ändringar i beteendet - dynamiskt!<br />
● Hur ska vi få vårt system att fungera olika efter<br />
olika metodanrop / olika tidpunkter?<br />
● Dvs, hur ska vi få tillståndsmaskinen in i<br />
systemet?
Implementationsidéer<br />
● Switch-tabell inne i klassen<br />
– Oöverskådligt när det blir för<br />
stort<br />
● Vad som helst inne i klassen<br />
– Råddigt, oöverskådligt...<br />
● Något annat?<br />
class CokeMachine {<br />
int state;<br />
public void insertCoin() {<br />
switch (state) {<br />
case STATE1:<br />
state = ...;<br />
break;<br />
case STATE2:<br />
state = STATE3;<br />
increaseSum();<br />
break;<br />
...<br />
}<br />
...<br />
}
State<br />
● De olika tillstånden representeras som<br />
subklasser<br />
– Måste finnas något sätt att signalera från tillstånden<br />
att kontexten skall byta tillstånd
Implementation via olika subklasser<br />
class CokeMachineState2 implements State {<br />
public void insertCoin(Context myCokeMachine) {<br />
myMachine.increaseSum();<br />
myMachine.playSound();<br />
myMachine.switchState(moneyInserted);<br />
}<br />
...<br />
}<br />
● Men det bästa är att ha ett verktyg som<br />
genererar tillståndsmaskinen från grafiska<br />
representationen
Problem - QuakeBot<br />
● Vi vill ha datorspelare som spelar på olika sätt<br />
– Roligare säljer bättre mer kul & fyrk
Strategy<br />
● Vi enkapsulerar valet av strategi<br />
● Olika datorspelar har gemensam<br />
huvudfunktionalitet (ev. fungerar enligt<br />
samma villkor/regler), men strategin<br />
gör att de reagerar på olika sätt
Skillnader mellan State och Strategy<br />
● Strategy är en samling algoritmer eller idéer för<br />
att nå något mål<br />
– Vi byter oftast inte strategi<br />
● State är en algoritm i ett visst specifikt tillstånd<br />
– Vi byter tillstånd enligt behov
Andra exempel<br />
● Strategimönstret är inte bara för spel<br />
● I mindre skala: en sorteringsrutin sorterar enligt<br />
ett visst kriterium – sorteringsstrategin!<br />
– Alla rutiner delar på gemensamma regler (= sortering<br />
och dess slutresultat)<br />
– Men de reagerar på olika sätt (= kriteriet)
Problem – bara små förändringar<br />
när vi subklassar<br />
● Ibland skapar man klasshierarkier där<br />
subklasserna<br />
– Ändrar de definierade metoderna väldigt lite<br />
– Måste göra vissa saker i rätt ordning<br />
class HamPizza {<br />
void bakeThePizza() {<br />
makeDough();<br />
addTomatoSauce();<br />
addSpices();<br />
addIngredients();<br />
addCheese();<br />
insertIntoOven();<br />
wait();<br />
removeFromOven();<br />
}<br />
}<br />
class SalamiPizza {<br />
void bakeThePizza() {<br />
makeDough();<br />
addTomatoSauce();<br />
addIngredients();<br />
addSpices();<br />
addCheese();<br />
insertIntoOven();<br />
wait();<br />
removeFromOven();<br />
}<br />
}
class Pizza {<br />
final public void bakeThePizza() {<br />
makeDough();<br />
addTomatoSauce();<br />
addSpices();<br />
addIngredients();<br />
addCheese();<br />
insertIntoOven();<br />
wait();<br />
removeFromOven();<br />
}<br />
abstract protected void makeDough();<br />
}<br />
Vårt templat<br />
protected void addTomatoSauce() {<br />
; // add some tomate sauce<br />
}<br />
Template Method<br />
class HamPizza {<br />
protected void makeDough() {<br />
...<br />
}<br />
protected void addTomatoSauce() {<br />
...<br />
}<br />
}<br />
Defaultbeteendet<br />
är<br />
inte OK;<br />
subklassen<br />
måste definiera<br />
metoden<br />
Defaultbeteendet<br />
kan<br />
vara OK; inget<br />
undantag
Template Method<br />
● Vacker användning av Open-Closed-principen!<br />
● Tvingar subklasserna att göra saker i en viss<br />
ordning<br />
● Håller kontrollen i superklassen<br />
● Själv templatet innehåller de nödvändiga<br />
delarna, och genom omdefinierbara<br />
metodanrop fås variation från subklasserna
Skillnader mellan Template Method<br />
och Strategy<br />
● Template Method<br />
– Håller kontrollen i superklassen<br />
– Gemensam kod kan återanvändas i superklassen<br />
● Strategy implementerar “något större”, behöver<br />
mer datafält osv<br />
– En strategi är en algoritm eller flera relaterade<br />
algoritmer; strategierna är en familj av utbytbara<br />
algoritmer<br />
– Kontrollen inte viktig; strategierna får göra hur de<br />
vill för att uppnå målet<br />
– Kod brukar inte återanvändas mellan klasser
Problem – anrop av metod enligt<br />
parameterns typ<br />
● Vi har följande klasshierarki...
● ...och ett objektdiagram enligt det<br />
● Analyseraren skall gå<br />
igenom alla noder och<br />
analysera dem enligt typ<br />
● Dvs, vi vill ha mer<br />
funktionalitet för varje<br />
klass utanför<br />
klassdefinitionerna
Första försöket<br />
class Analyzer {<br />
public void iteratePart(CarPart node) {<br />
analyze(node);<br />
for (CarPart c : node.getChildren()) {<br />
iteratePart(c);<br />
}<br />
}<br />
}<br />
private void analyze(CarPart c) {<br />
// Hmm, I wonder if it's a Seat, an Engine or something else?<br />
if (c instanceof Engine) {<br />
// Check Engine data<br />
} else if (c instanceof Seat) {<br />
// Check Seat data<br />
} ...<br />
}<br />
● Problem: vi måste vara medvetna om alla<br />
subklasser!
Vad vi behöver<br />
class Analyzer {<br />
public void visit(Engine e) {<br />
// Check Engine data<br />
}<br />
public void visit(Seat s) {<br />
// Check Seat data<br />
}<br />
...<br />
}<br />
● Om vi glömmer en subklass, eller om en ny<br />
subklass skapas, kommer kompilatorn att<br />
klaga, för det finns ingen “catch-all” i stil med<br />
visit(CarPart c)
Lösning<br />
abstract class CarPart {<br />
public ArrayList parts = new ArrayList();<br />
public void iterateParts(Analyzer a) {<br />
accept(a);<br />
for (CarPart c : this.getChildren()) {<br />
c.iterateParts(a);<br />
}<br />
}<br />
abstract protected void accept(Analyzer a);<br />
}<br />
class Engine extends CarPart {<br />
protected void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
class Seat extends CarPart {<br />
protected void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
...<br />
class Analyzer {<br />
public void visit(Engine e) {<br />
// Check Engine data<br />
}<br />
public void visit(Seat s) {<br />
// Check Seat data<br />
}<br />
...<br />
}<br />
● => Kompilatorn klagar om vi<br />
glömmer en subklass från<br />
Analyzer!
Lösning<br />
abstract class CarPart {<br />
public ArrayList parts = new ArrayList();<br />
public void iterateParts(Analyzer a) {<br />
accept(a);<br />
for (CarPart c : this.getChildren()) {<br />
c.iterateParts(a);<br />
}<br />
}<br />
abstract protected void accept(Analyzer a);<br />
}<br />
class Engine extends CarPart {<br />
2<br />
1<br />
protected void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
class Seat extends CarPart {<br />
protected void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
...<br />
class Analyzer {<br />
public void visit(Engine e) {<br />
// Check Engine data<br />
}<br />
public void visit(Seat s) {<br />
// Check Seat data<br />
}<br />
...<br />
}<br />
3<br />
● Märk att genomgång av<br />
strukturen (1) är skild<br />
från accept-metoderna<br />
(2)<br />
● Den nya funktionaliteten<br />
ges i (3)
Eller ett enkelt alternativ...<br />
class CarPart {<br />
public ArrayList parts = new ArrayList();<br />
abstract public void accept(Analyzer a);<br />
}<br />
class Engine extends CarPart {<br />
public void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
class Seat extends CarPart {<br />
public void accept(Analyzer a) {<br />
a.visit(this);<br />
}<br />
}<br />
...<br />
class Analyzer {<br />
public void iterateParts(CarPart node) {<br />
node.accept(this);<br />
for (CarPart c : node.getChildren()) {<br />
iterateParts(c);<br />
}<br />
}<br />
public void visit(Engine e) {<br />
// Check Engine data<br />
}<br />
public void visit(Seat s) {<br />
// Check Seat data<br />
}<br />
...<br />
}
Visitor<br />
● Vi har tilläggt ny<br />
funktionalitet till<br />
klasshierarkin utan att<br />
funktionaliteten finns i<br />
subklasserna<br />
– Alla subklasser måste dock<br />
ha en skild identisk metod<br />
● Iterering genom strukturen<br />
skild från acceptans =><br />
olika möjligheter till iteration
Problem – hur får vi double dispatch<br />
till Java / C# / C++ ?<br />
A a = new A();<br />
P p = new P();<br />
a.m(p);<br />
A a = new B();<br />
P p = new P();<br />
a.m(p);<br />
A a = new A();<br />
P p = new U();<br />
a.m(p);<br />
● Vilken metod kommer egentligen att anropas?<br />
– (Dvs,vilka omdefinitionsmöjligheter har vi?)<br />
A a = new B();<br />
P p = new U();<br />
a.m(p);
Java / C# / C++ är single dispatch<br />
● Single dispatch:<br />
– a.m(p) anropas enligt den dynamiska typen av a<br />
– a.m(p) anropas inte enligt den dynamiska typen av<br />
parametern p – om p är ett P- eller U-objekt<br />
påverkar inte<br />
● Dvs, enbart A.m(P p) eller B.m(P p) kommer att<br />
anropas<br />
● Dvs, det är inte möjligt att anropa en skild rutin<br />
om vi använder oss av ett U-objekt
Det intressanta i vårt exempel<br />
● Vi kan göra subklasser till Analyzer<br />
– Vi har alltså flera CarPart-objekt och något<br />
Analyzer-objekt<br />
● Därmed kommer visit-metoderna att anropas<br />
– Enligt den dynamiska typen på CarPart-objektet<br />
– Enligt den dynamiska typen på Analyzer-objektet<br />
● Double dispatch
● En ny subklass till<br />
CarPart kommer<br />
att kräva en ny<br />
visit-metod i<br />
Analyzer<br />
● En ny (abstrakt)<br />
visit-metod<br />
kommer att kräva<br />
en omdefiniering i<br />
varje subklass till<br />
Analyzer<br />
class Analyzer {<br />
public void iterateParts(CarPart node) {<br />
node.accept(this);<br />
for (CarPart c : node.getChildren()) {<br />
iterateParts(c);<br />
}<br />
}<br />
abstract public void visit(Engine e);<br />
abstract public void visit(Seat s);<br />
...<br />
};<br />
class MyAnalyzer extends Analyzer {<br />
public void visit(Engine e) {<br />
...<br />
}<br />
public void visit(Seat s) {<br />
...<br />
}<br />
};
Visitor<br />
● Behövs inte så ofta, men när det behövs är det<br />
nyttigt<br />
– Tillägg av funktionalitet till klasshierarkier, utan att<br />
man vill fylla klasserna med metoder som inte<br />
verkar höra hemma där<br />
– T.ex. kollisionsdetektion mellan olika figurer<br />
● Lisps generiska funktioner löser det här<br />
snyggare