23.07.2013 Views

Beteendemässiga designmönster

Beteendemässiga designmönster

Beteendemässiga designmönster

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!