Design Pattern 2 [FF 6 – 11]
Feb 25, 2016
Design Pattern 2[FF 6 – 11]
2
Remember Starbuzz Coffee?
Coffee instruction1. Boil water2. Brew coffee in boiling water3. Pour the mix in cup4. Add sugar and milk
Tea instruction1. Boil water2. Put tea leaves in boiling water3. Pour the mix in cup4. Add lemon
CoffeeboilWater()brewCoffee() pourMix()addSugarMilk()prepare()
TeaboilWater()brewTea() pourMix()addLemon()prepare()
prepare() { boilWater() brewCoffee() pourMix() addSugarMilk()}
Can we improve this design?
3
Factor out common things to a superclass
CoffeebrewCoffee() addSugarMilk()prepare()
TeabrewTea() addLemon()prepare()
<<abstract>>HotBeverageboilWater()pourMix()prepare() // abs
prepare() { boilWater() brewCoffee() pourMix() addSugarMilk()}
prepare() { boilWater() brewTea() pourMix() addLemon()}
What else can be improved?
prepare() { boilWater() brew () pourMix() addCondiments()}
you can even finalize it
4
The new design
Coffeebrew() addCondiments ()
Teabrew() addCondiments()
<<abstract>>HotBeverageboilWater()pourMix()prepare()brew()addCondiments()
“prepare” is no longer
abstract!
final prepare() { boilWater() brew () pourMix() addCondiments()}
So how easy is it now to add Chocolate??
The method “prepare” is a so-called “template method”; “brew” and “addCondiments” are called “hooks”. So this “pattern” here is called “Template Method Pattern”.
5
Another examplepublic class MyApplet extends Applet {
public void init() { start to pre-load that film! load main image repaint() }
public void stop() { if film is playing, stop it }
public void start() { continue film }
public void paint() {...}}
These are hooks, used by a number of template methods in the superclass Applet.
6
Generic remote control
• We need to design this generic RC for HomeAutomation Inc• Slots must be dynamically assignable• HA provides a bunch of “devices”• Unfortunately they have varying interface• Furthermore ... expect more devices will come in the future
RemoteControlslots : Device[8]assignSlot(slotNr,device)turnOn(slotNr)turnOff(slotNr)undo()
OutdoorLightturnOn()turnOff()
StereoturnOn()putcd()setvolume(v)turnOff()
CeilingFanlow()medium()high()off()getSpeed()
Device
7
First solution
• Behavior that dynamically depends on subtypes• Remember the Factory solution ?• We need to do something similar, but with dynamic behavior
selection. • (this presentation deviates a bit from FF)
RemoteControlslot : Device[8]assignSlot(slotNr,device)turnOn(slotNr)turnOff(slotNr)undo()
turnOn(n) { d = slot[n] if d is an OutdoorLight then d.turnOn() else if d is a Stereo then { d.turnOn() d.putCD() d.setVolume(10) } else ...}
8
Encapsulating ...
We encapsulate each “sub-type” of behavior in its own “Command” class.
RemoteControlonCmds : Command[8]offCmds : Command[8] assignSlot(slotNr,device)turnOn(slotNr)turnOff(slotNr)undo()
Device LightOnCmd
Commandexecute()
LightOffCmd
StrereoOnCmd
StrereoOffCmd
our “behavior factory method” :
assignSlot(n,device) { onCmds[n] = device.createOnCmd() ; offCmds[n] = device.createOffCmd() ; }
turnOn(n) { onCmds[n].execute() }
turnOff(n) { offCmds[n].execute() }
9
How client requests are now handled..
DeviceOutdoorLightOnCmd
Commandexecute()
OutdoorLightOffCmd
StrereoOnCmd
StrereoOffCmdexecute() { d = (Strereo) device d.turnOn() ; d.putCD() ; d.setVolume(10)}
execute() { d = (OutdoorLight) device d.turnOn() ;}
:RC
turnOn(1)execute() turnOn()
putCD()setVolume(10)
:Command :Device
OutdoorLightOnCmd OutdoorLight
10
Adding Fan-commands and Undo
DeviceOutdoorLightOnCmd
Commandexecute()undo()
OutdoorLightOffCmd
FanOnCmd
FunOffCmd
execute() { d = (CeilingFan) device speed = d.getSpeed() if speed is 0 then d.low() else if speed is low then d.medium() … oldstate = speed}
undo() { (OutdoorLight) device . turnOff() }
FanCmdoldState
undo() { d = (CeilingFan) device if oldstate is off then d.turnOff() else if oldstate is low then d.low() ... }
11
Now implementing “undo” on the RC ...
RemoteControlonCmds : Command[8]offCmds : Command[8] assignSlot(slotNr,device)turnOn(slotNr)turnOff(slotNr)undo()- lastCmd : Command
turnOn(n) { c = onCmds[n] c.execute() lastCmd = c}
undo() { lastCmd.undo() lastCmd = new DoNothingDevice()}
12
The “Command Pattern”Receiveroperation1operation2
Concrete Cmd 1<<interface>>Commandexecute()undo() Concrete Cmd 2
Invoker
Client
<<create>>
Device
RemoteControl
Encapsulate behavior as an object; like normal object, these objects can be e.g. grouped together, send, and manipulated, before we execute the behavior they represent.
(different formulation than FF)
Device
13
Example of things you can do
• You can stream commands, and have multiple threads consuming them
• logging and reload useful when the “invoker” crashes
MacroCmdcmds : Command[]execute()undo()
execute() { for (i=0; i<#cmds; i++) cmds[i].execute()}
undo() { for (i=#cmds; 0<i; i--) cmds[i-1].undo()}
14
The merging of 3 restaurants...
• They can agree on a common class for “menu item”• But refuse to change how the menus are internally represented too
much of their respective software already depend on it
MenuItemnamedescisVeggieprice
DinnerMenumenu : MenuItem[]addItem(item)
PancakeHouseMenumenu : ArrayListaddItem(item)
CafeMenumenu : HashTableaddItem(item)
MenuWebInterfaceprintFullMenu()printVeggies()printLowBudget()
*
*
*(“Waitress” in your book)
15
Let’s now implement the MenuWebInterface …
printFullMenu() { dinnerItems = dinnerMenu.items // array for (i=0; i<dinnerItems.size; i++) print (dinnerItem[i]) lunchItems = pancakeHouseMenu.items // listarray for (i=0; i<lunchItems.size(); i++) print ((MenuItem) lunchItems.get(i))
drinks = cafeMenu.items.getValues() // values of hash-table for (i=0; i<drinks.size(); i++) print ((MenuItem) drinks.get(i))}
Similar iterations in implementing the other print operations
16
Encapsulating iterationsDinnerMenu- menu : MenuItem[]addItem(item)
PancakeHouseMenu- menu : ArrayListaddItem(item)
<<interface>>IteratorhasNext() : boolnext() : Object
MenugetName()createIterator()
<<create>>
(deviate a bit from FF)
// you can hide it now
DinnerMenuIterator
PancakeMenuIterator class DinnerMenuIterator extends Iterator { MenuItem[] items int ctr
DinnerMenuIterator(items) { this.items = items ; ctr = -1 }
hasNext() { return ctr < #items - 1 } next() { ctr++ ; return items[ctr] }}
createIterator() { return new DinnerMenuIterator(menu)}
Iterator pattern
(abs. class)
17
Reworking MenuWebInterfaceMenuWI
<<interface>>MenugetName()createIterator()
* menus
printMenu() { for (Menu m : menus) { print m.getName() iter = m.getIterator() while (iter.hasNext()) { i = (MenuItem) iter.next() ; print(i) ; }}
: MenuWI : DinnerMenu
: DinnerMenuIterator
printMenu()createIterator() <<create>>
hasNext()
next()
etc …
18
Iterator in Java
• Using the pattern amounts to subclass it• Standard collection classes already come with their iterator-classes.• “remove” is optional. It removes the element the element you just get from next() ; can only be
called once per next() call.• if you change the underlying ‘collection’ during an iteration by any other way than using
remove(), the behavior of the Iterator is undefined.
<<interface>>IteratorhasNext() : boolnext() : Objectremove() : void
Java.Util.Interface
i = V.iterator() ;while (i.hasNext()) { x = i.next() ; if (x == Bob) V.add(Patrick) ; print(x) ;}
// will Patrick be printed ?
19
While we are talking about Java iterators...
<<interface>>IteratorhasNext() : boolnext() : Objectremove() : void
<<interface>>EnumerationhasMoreElements() : boolnextElement() : Object
Before Java 1.2 :
Suppose we have Java 1.1 legacy code; new features are to be added in 1.6. In the new part we prefer to use Iterator; but how to upgrade your old Enumeration classes to Iterators?
EnumerationAdapterconstructor(enumeration)
1.6Client
<<use>>1
Concrete EnumerationA
20
Implementation<<interface>>IteratorhasNext() : boolnext() : Objectremove() : void
<<interface>>EnumerationhasMoreElements() : boolnextElement() : Object
EnumerationAdapterenumconstructor(enumeration)
1.6Client
<<use>>1
Concrete EnumerationA
EnumerationAdapter(e) { enum = e }
enum
hasNext() { return enum.hasMoreElements() }next() { return enum.nextElement() }remove() ...??
For client now to iterate over A :
e = instanceA.getEnum()i = new EnumerationAdapter(e)
AgetEnum() : ConcreteEnumerationA
21
That was the “adapter pattern”
Targetoperations
Adapteeother operations
Adapter
Client<<use>>
1
Iterator
Enumeration
• We’ll discuss two more variants: Proxy and Facade.• Not too complicated
22
Adding a reporting function to parking machines
ParkingMachinelocation()count()hours()
Report+ printReport ()
printReport() { for (Machine m : machines) { print m.location() print m.count() print m.hours() }}
machines
Does not work if the parking machines objects exist on physically different computers.
We need a networking solution, but want a nice way to fit that into our design.
*
23
Proxy pattern
Report+ printReport ()
ParkingMachinelocation()count()hours()
<<interface>>ParkingMachineInterfacelocation()count()hours()
ParkingMachine
ParkingMachineProxy
client sideremote side
The proxy pass requests to the actual subject; implying it implements this passing mechanism, e.g. over Internet
Create a “stand-in” for another object; useful when the real object is remote, or expensive to create, or require securing.
subject
real subject
*
24
Home cinema
25
Ok.. let’s watch a film now
• Complicated...• How about when we are
done watching film.• How about listening to
music? etc• What if we upgrade the
system?
26
Facade pattern
To provide a simplified interface.
HomeCinemaFacedewatchFilm()listenCD()
Client<<use>>
So... can you say when it is more appropriate to use one of these over the other ?• Adapter• Proxy• Facade• Decorator
27
Candy machine…
no coin
has coin
sold
empty
inser
t coin
turn crank
/ [counter is updated]
[not empty]
[empt
y]CandyMachine- state+ insertCoin(c)+ turnCrank()+ getCandy()
get candy
insert coin
turn crank
get candy[1eur]
[else
] / [
coin
is e
ject
ed]
28
Mapping this to implementation
class CandyMachine { static final NOCOIN = 0 static final HASCOIN = 1 static final SOLD = 2 static final EMPTY = 3
private state private n
CandyMachine() { this.n = 200 state = NOCOIN }}
insertcoin(c) { if (state == NOCOIN) { if (c is 1 euro) state = HASCOIN else eject c } else eject c}
getcandy() { if (state == SOLD) { n-- if (n==0) state = EMPTY else state = NOCOIN } else skip}
29
The company wants to add usr messaging
insertcoin(c) { if (state == NOCOIN) { if (c is 1 euro) state = HASCOIN else { print “Not 1 euro” eject c } } else if (state == HASCOIN || state == SOLD) { print “You have inserted a coin” eject c } else if (state == EMPTY) { print “the machine is empty” eject c }}
getcandy() { if (state == SOLD) { if (n==0) state = EMPTY else state = NOCOIN } else if (state == NOCOIN) print “You have not inserted a coin” else if (state == HASCOIN) print “You =have not turned the crank” …}
30
The company wants to add prize ball!
no coin
has coin
sold
empty
inser
t coin
turn crank
/ [counter is updated]
[not empty]
[empt
y]
get candy
[1eur]
[else
] / [
coin
is e
ject
ed]
bonus
get candy
turn crank[n>1]
/ [counter is updated]
Implementationblows!
turn crank
/ [counter is updated]
31
Encapsulating state
CMstateinsertCoin(c)turnCrank()getcandy()
NoCoinSt
HasCoinSt
SoldSt
EmptySt
candyMachine- n : int+ insertCoin(c)+ turnCrank()+ getcandy()
(current) state
1
owner
0..1
candyMachine() { n = 200 state = new NoCoin(this)}
NoCoinSt(machine) { owner = machine}
insertCoin(c) { state.insertCoin(c) }turnCrank() { state.turnCrank() }getcandy() { state.getcandy() }
Simple candy Machine !
(abstract class)
32
Implementing the states
CMstateinsertCoin(c)turnCrank()getcandy()
NoCoinSt
HasCoinStcandyMachine 1
owner
0..1 …
insertCoin(c) { if (c is 1 euro) owner.state = new HasCoinSt(owner) else { print “not 1 euro coin” ; eject c }}
insertCoin(c) { print “you have inserted a coin” ; eject c }}
getcandy() { print “you have not inserted a coin”}
getcandy() { print “you must first turn the crank” }}
insertcoin(c) { if (state == NOCOIN) { if (c is 1 euro) state = HASCOIN else { print “Not 1 euro” eject c } } else if (state == HASCOIN || state == SOLD) { print “You have inserted a coin” eject c } else if (state == EMPTY) { print “the machine is empty” eject c }}
getcandy() { if (state == SOLD) { if (n==0) state = EMPTY else state = NOCOIN } else if (state == NOCOIN) print “You have not inserted a coin” else if (state == HASCOIN) print “You =have not turned the crank” …}
33
What is the point of this?
CMstateinsertCoin(c)turnCrank()getcandy()
NoCoinSt
HasCoinSt
SoldSt
EmptySt
candyMachine- n : int+ insertCoin(c)+ turnCrank()+ getcandy()
(current) state
1
owner
0..1
BonusSt
What need to be done ?
• Obviously implement BonusSt this implements all arrows leaving Bonus• We still have to code incoming arrows turnCrank of HasCoinSt
The changes in the existing code is minimized!
34
State pattern
Extensible implementation of state-machine.(dev. from FF)
Stateoperation1(..)operation2(..)
Concrete State 1
Concrete State 2
StMachineoperation1(..)operation2(..)
(current) state
1
owner
0..1…
<<interface>>Statehandle(..)
Concrete State 1
Concrete State 2
Contextrequest(..)
…request(..) { state.handle(..) }
FF:
35
Back to our restaurants…
DinnerMenu
PancakeHouseMenuMenu
(deviate a bit from FF)
CafeMenuMenuItem*
36
What if we plan to add submenus in the future?all menus
: DinnerMenu : PancakeMenu : CafeMenu
MenuItems
: Dessert Menu
We need a tree structure.And a convenient and extensible way to implement computations that traverse the tree, like:• Counting the menu items• Get the cheapest menu item• Checking if a menu item is present
37
Composite pattern
Componentoperation(..)add(component)remove(component) getChild(k)
Compositeoperation(..)add(component)remove(component) getChild(k) // or getChildren() .. or ret. iterator
Leafoperation(..)
* components
:Composite
:Composite:Leaf :Leaf
:Leaf :Leaf
:Composite
:Leaf :Leaf
(FF’s formulation of the pattern)
operation e.g. get all leaves
38
DiscussionComponentoperation(..)add(component)remove(component) getChild(k)
Compositeoperation(..)add(component)remove(component) getChild(k)
Leafoperation(..)
* components
• Forcing some operations on Leaf• Circularity• getChild does not seem very useful
children () : List<Component>(alternatively, return an iterator)
39
Implementation
<<interface>>MenuComponentoperation(..)
Menuadd(component)remove(component)
MenuItemnamedescprice
* components
class Menu { components = new LinkedList<...>()
add(c) { components.add(c) } remove(c) { components.remove(c) }
}
40
Operation : counting menu items
<<interface>>MenuComponentcountMenuItems()
Menuadd(component)remove(component)
MenuItemnamedescprice
* components class Menu { components …
countMenuItems() { n = 0 for (MenuComponent m : components) n += m.countMenuItems()) return n }}
class MenuItem { countMenuItems() { return 1 }}
41
Operation : getting the cheapest menu item
<<interface>>MenuComponentcountMenuItems()getCheapest()
Menuadd(component)remove(component)
MenuItemnamedescprice
* components class Menu { components …
getCheapest() { p = MAXINT c = null for (MenuComponent m : components) { d = m.getCheapest() if (d != null && d.price < p) { c = d ; p = d.price } } return c }}
class MenuItem { getCheapest() { return this }}