Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]
description
Transcript of Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]
Design Pattern 1[Eric Freeman & Elisabeth Freeman 1 – 5 ]
2
Design of “simDuck” , 1st attempt
Duckdisplay()quack()
RedHeadDuckdisplay()
WildDuckdisplay()
RubberDuckdisplay()fly()
DecoyDuckdisplay()quack()fly()
rubber doesn’t fly; decoy doesn’t quack nor fly.
Inheritance allows you to reuse code, but also “forces” attributes and behavior to the subclasses.
• Maintenance is expensive!• Software evolves; is your design flexible enough??
Client
<<use>>
fly()
3
2nd attempt: how about factoring out to multiple interfaces?
Duckdisplay()
RedHeadDuckdisplay()quack()fly()
WildDuckdisplay()quack()fly()
RubberDuckdisplay()quack()
DecoyDuckdisplay()
<<interface>>QuackBehaviorquack()
<<interface>>FlyBehaviorfly()
• Unfortunately, now you lose code reuse; bad for maintenance ...
• Use multiple inheritance instead? Well, not supported in all OO languages.
4
FF proposes a number of “design principles”
• Separate and encapsulate varying aspects from constant ones– “varying” across instances
• Program against “interface” rather than implementation– relying on the signature of a superclass allow you the
flexibility to replace its instances with those of subclasses– for extensibility
• Consider using “composition” over inheritance as an option
5
Separating and encapsulating quack and fly
<<interface>>QuackBehaviorquack()
<<interface>>FlyBehaviorfly()
StandardQuackquack()
Squeakquack()
StadardFlyfly()
noFlyfly()
Duckdisplay()quack()fly()
RedHeadDuckdisplay()
WildDuckdisplay()
RubberDuckdisplay() ...
...
has
has
quack() { quackbehavior.quack() }
quackbehavior
Programming against “interface”; Duck does not care which actual quack-behavior you supply; any subclass is just as good.
6
Composition instead of inheritence
<<interface>>QuackBehaviorquack()
<<interface>>FlyBehaviorfly()
StandardQuackquack()
Squeakquack()
StadardFlyfly()
NoFlyfly()
RedHeadDuckdisplay()
WildDuckdisplay()
RubberDuckdisplay() ...
...
has
has
Duck now gets some behavior from “composition” rather than inheritance. Composition also has the advantage of “can be changed dynamically” (but on the other hand you lose some static checking).
Duckdisplay()quack()fly()
changeFly(f : FlyBehavior) { flybehavior = f}
flybehavior
7
Design pattern
<<interface>>Strategyalgorithm(...)
Strategy-1algorithm(...)
Strategy-2algorithm(...)
SomeClasssome_method(...)
“Strategy Pattern”
...
• Is a solution “pattern” for a certain problem; in OO typically problems around the “flexibility” of your design.
• Unfortunately often cannot be implemented as library, nor do we have a satisfactory formalization of them.
• Also (very) useful as common vocab for engineers to communicate solutions.
8
Books
1995, “Gang of 4”Catalog of 23 patternsClassic book in SE, sold over 0.5 M copies.
2004Eric Freeman & Elisabeth FreemanMuch better explanation, good reviews!(Some disagreement)Do read it with “open mind”
9
A bit unortodox...
10
Weather “plugins”
s : WeatherStation
provides live data on temp, humidity, pressure.
w : WeatherDataPro
vider
d : WeatherDisplay
get data
Three kinds of displays:• current weather• weather statistics• predictionHave to be regularly updated.
11
Design
WeatherDataProvider- temp- humidity- pressurenotify()setMeasurement(t,h,p)
temp = thumidity = hpressure = pnotify()
currentWeather.update(temp,humidity,pressure)weatherStat.update(temp,humidity,pressure)forecast.update(temp,humidity,pressure)
CurrentWeatherDisplayupdate(t,h,p)
WeatherStatDisplayupdate(t,h,p)
ForecastDisplayupdate(t,h,p)
12
Observer pattern
Observerupdate(data)
SubjectaddObserver (o)removeObserver(o)notifyObservers()setState(...)
observerssubject
*0..1
subjectclient obs1 obs2
setState(..) notifyObservers(..)
update(data)update(data)
13
Should we push data, or let observers pull them?
Observerupdate(data)update(subject)
SubjectaddObserver (o)removeObserver(o)notifyObservers()setState(...)getState() : ...
observerssubject
*
s : Subjectclient obs
setState(..) notifyObservers(..)
update(s)
s.getState ()
do something with data...data
14
Abstract or interface?
Observerupdate(data)update(subject)
SubjectaddObserver (o)removeObserver(o)notifyObservers()setState(...)getState()
*
Doesn’t work if you don’t have multiple inheritance Making Subject an <<interface>> does not solve the problem (now we can’t inherit!).Puzzle for you: propose a solution for this.
<<interface>>
SubjectImpl 1
SubjectImpl 2
ObserverImpl 1
ObserverImpl 2
Nice, so you can instantiate this pattern by subclassing it!But ….
15
Java provides the pattern as “classes” !
<<interface>>Observerupdate(o : Observable, data: Object)
ObservableaddObserver (obsvr)removeObserver(obsvr)notifyObservers()notifyObservers(data)setChanged()
*
WeatherDataProvider
WeatherDisplay
CurrentWeatherDisp
WeatherStatDisp
ForecastDisp• Good thing about this is that you can use the pattern through subclassing/impl.• Does have its limitation...
16
Starbuzz Coffee, initial design
Beveragedesccost()
Espressocost()
Decafcost()
Darkroastcost()
Houseblendcost()
Each implement its own cost calculation
But what if the business expands and wishes to offer more choices; e.g. with condiments steamed milk, soy , mocha, whipped cream ?
Pure inheritance-based approach:
SoyDarkroastWithWhipcost()
17
Maintenance??
18
Ok, so that not good … ; how about this:
Beveragedescmilkcocoasoywhipcost()
Espressocost()
Decafcost()
Darkroastcost()
Houseblendcost()
Name change factors that may impact this design:• Change in condiment prices• You want to add new condiments• New kind of beverage (e.g. tea) some condiments are inappropriate• How about double mocha?
Open-closed principle: classes should ideally open for extension but closed for modification.
19
Ideas
• Factor out varying factors; exploit compositions:
• Make condiment a feature you can wrap around a beverage:
• Beter yet, make them stackable:
Milk CocoaSoy WhipBeverage
d : DarkroastWhip
d : Darkroast
20
Decorator pattern
Componentoperation(…)
Concrete Componentoperation(…)
Decoratoroperation(…)
ConcreteDecorator Aoperation(…)
ConcreteDecorator Boperation(…)
Client <<use>>
Beverage
Esperesso, Darkroast,Decaf
Milk, Cocoa, Whip
Condiment
21
Distributing the behavior
Beveragecost()
Espressocost()
Condiment
Cocoacost()
Whipcost()
component class Espresso extends Beverage { cost() { return 1.50 euro }}
class Cocoa extends Condiment { component : Beverage Cocoa (b:Beverage) { component = b } cost() { return b.cost() + 50 cent }}
How to make double mocha espresso :
new Cocoa( new Cocoa (new Espresso())
Notice how the “cost” functionality propagates over your decorators-stack.
How about adding “Tea” and “rum” ? Can we keep the Open-Closed principle?
e : esperessocost cost
22
Real world decorators: Java I/O
<<abstract>>InputStream
FileInputStream
ByteArrayInputStream
<<abstract>>FilterInputStream
BufferedInputStream
LineNumberInputStream (Depracated!)
DataInputStream
So, you can stack your InputStream decorators.And even make your own decorators (by subclassing FilterInputStream)
23
Your question
Beveragecost()
Espressocost()
Condiment
Cocoacost()
Whipcost()
Beveragecost()
Espressocost()
Condiment
Cocoacost()
Whipcost()
0..1
0..1
VS
In the Right solution, cost() in Beverage has to anticipate the cost-logic of condiments. This might do:
cost() { return 1.50 + condiment.cost() }
but this already presumes that cost should be just additive.
Whereas in the Decorator-solution, each condiment can use and override the cost-logic of its beverage-base.
24
Late/dynamic binding
• Many OO languages allows the type of the object created to be decided “late” (at the run-time)
• Implies that behavior can also be bound dynamically• Pro: gives you a lot of flexibility• Cons: you lose some static checking
cookPizza(availableBudget) { Pizza p if availableBudget < 10 euro then p = new MargheritaPizza() else p = new PeperoniPizza() p.cook() return p}
25
Pizza storePizzaStoreview()order()
Pizzaview()prepare()bake()box()
Margherita
Peperoni
Veggie
A well-proven “order” algorithm:
order() { p = new Pizza() p.prepare() p.bake() p.box() return p}
order(type) { if type=“Margherita” p = new Margherita() else if type=“Peperoni” p = new Peperoni() else p = new Veggie() p.prepare() p.bake() p.box() return p}
view(type)order(type)
Now we want to add more types of pizzas, and be able to order different types of pizzas.
• Similar creation routine for view(type) •As you can see, object creation can involve a more complex logic.• The above works… but notice that you program against implementation; disfavoring flexibility…
26
Separating and encapsulating the “sub-type dependency”-part
PizzaStoreview(type)order(type)
Pizzaview()prepare()bake()box()
Margherita
Peperoni
Veggie
PizzaFactorycreatePizza(type)
order(type) { p = factory.createPizza(type) p.prepare() p.bake() p.box() return p}
createPizza(type) { if type=“Margherita” p = new Margherita() else if type=“Peperoni” p = new Peperoni() else p = new Veggie() return p}
Aren’t we just moving piece of code around?? Yes, but note that there were multiple places (order & view) that need “createPizza” ; now we have put the behavior in a single place.
27
“Factory”
• A “factory” encapsulates a subtype-dependent object creation in one place.
• “PizzaFactory” is a factory class• But actually, it is the operation “createPizza” that does the
work factory method• We can also put this method elsewhere, e.g. we could have
put “createPizza” in the pizza class.• We’ll see an example of a “factory method solution” next
PizzaFactorycreatePizza(type)
28
Let’s now franchise the pizza store…
PizzaStoreview(type)order(type)
NYPizzaFactory
ChicPizzaFactory
• NY and Chicago want to franchise ok, we just create instances of PizzaStore for them.• After sometime, the branches want to introduce their own “local” variants :• NY pizzas: thin crust, tasty sauce, light cheese• Chicago pizzas: thick crust, rich sauce, much cheese
NYPizzaStoreview(type)order(type)
PizzaFactory
ChicPizzaStoreview(type)order(type)
order(type){ factory = new NYPizzaFactory() ; super.order(type) }
But now it may be tempting for the branches to override the standard “order algorithm”, e.g. to use local boxes to pack the pizzas. What if we want to impose more control on this?
override order to select the right pizza factory
29
Fixing the “order”, and putting a place holder for the variating part..
<<abstract>>PizzaStoreorder(type) // final ?createPizza(type) : Pizza // abs
NYPizzaStorecreatePizza(type)
ChicPizzaStorecreatePizza(type)
Pizzaprepare()bake()box()
NYMargheritaNYPeperoni
NYVeggie
ChicMargheritaChicPeperoni
ChicVeggie
We’ll fix the logic of “order”:
order(type) { p = createPizza(type) p.prepare() ; p.bake() p.box() return p}
We’ll move the fm createPizza to PizzaStore, and leave it for the branches to implement:
createPizza(type) { if type=“Margherita” p = new NYMargherita() else if type=“Peperoni” p = new NYPeperoni() else p = new NYVeggie() return p}
30
Factory Method pattern
<<abstract>>Creatoroperation(type) // createProduct(type) : Product // FM, abs
Concrete Creator AcreateProduct(type)
Concrete Creator AcreateProduct(type)
Productoperations
Product 1AProduct 2A
Product 3A
Product 1BProduct 2B
Product 3B
• Creator’s operation depends on subclass, but does not (want to) know apriori which subclass is used.• Provide better encapsulation than the 1st approach using a simple factory class
Factory method pattern is used to encapsulate the subtype-dependent creation-part of an operation. (different formulation than FF)
Pizza
NYMargherita,NYPeperoni,…
ChichMargherita,ChicPeperoni,…
PizzaStore
NYPizzaStore ChicPizzaStore
31
What a hassle! why don’t we just do it this way??
• Code duplication maintenance• Breaking open-closed principle adding branches force you
to change the code. (New solution still does that, but to a lesser degree; ‘break point’ at the factory-methods)
PizzaStoreview(branch,type)order(branch,type)
if branch=NY then if type = margherita then p = new NYMargherita() else if type = paperoni … …else if branch = Chicago then …
32
Dependency Inversion
• Mentioned in FF.• It is natural that a class depends on its parts. However this
also limits how you can combine/use this class in various settings.
• Inversion: try to decouple this dependency, hence making the class more composable.
33
On with the pizzas..
• As it is now, each subclass has full control on how to implement its own “prepare” too much freedom at the subclasses?
• Now suppose we want to put more “organization” into this:– Margherita should be prepared differently than Peperoni, but the preparation
should largely the same accross branches (NY,Chic, etc)– Branches only differ in e.g. ingredients used, each uses e.g. locally popular
substitutes for cheese, sauce, etc.• We’ll transform to a different design to facilitate this…
Pizzaprepare()
NYMargerithaNYPeperoni
NYVeggie
ChicMargerithaChicPeperoni
ChicVeggie
34
Let’s take a closer look…
NY MargheritaDough, mozzarella, plum tomato sauce, no-topping , no-meat
NY VeggieDough, mozzarella, plum tomato sauce,spinach, black-olive , no-meat
NY PeperoniDough, mozzarella, plum tomato sauce,spinach, black-olive , peperoni
Chicago MargharitaDough, reggiano cheese, marinara sauce, no-topping , no-meat
Chicago VeggieDough, reggiano, marinara sauce,onion, red-peper, no-meat
Chicago PeperoniDough, reggiano, marinara sauce,onion, red-peper, peperoni
35
Separate and encapsulate…
<<abstract>>Pizzaprepare() // abs
IngredientFactorycreateDough() : DoughcreateSauce() : SaucecreateCheese() : CheesecreateToppings() : Topping[]createMeat() : MeatMargherita
prepare()
NYIngredientFactory
ChicIngredientFactory
Peperoniprepare()
createSauce() { return new PlumTomatoSauce()}
createCeese() { return new Mozzarella()}
createSauce() { return new MarinaraSauce()}
createCeese() { return new Regiano()}
prepare() { sauce = ingredientFactory.createSauce() cheese = ingredientFactory.createCheese() meat = null …}
36
Abstract Factory Pattern
<<interface>>AbstractFactorycreateA() : Abstract Ingredient AcreateB() : Abstract Ingredient B...
ConcreteFactory NY
ConcreteFactory Chic
<<interface>AbstractIngredient A
NY-concrete AChic-concrete A
NY-concrete BChic-concrete B
<<interface>AbstractIngredient B
ingredientFacrory
Providing an abstract interface for creating a family of products.(abstract the interface does not expose coupling to concrete classes)
37
The store has to be a bit different as well..
<<abstract>>PizzaStoreorder(type) createPizza(type) : Pizza // abs
NYPizzaStorecreatePizza(type)
ChicPizzaStorecreatePizza(type)
Pizza
NYMargheritaNYPeperoni
NYVeggie
ChicMargheritaChicPeperoni
ChicVeggie
---------
createPizza(type) { if type == “margherita” then p = new Margherita(ifc) else if type == “Peperoni” then p = new Peperoni(ifc) …
order(type) { ifc = createIngrFactory() p = createPizza(type) p.prepare() ; p.bake() p.box() return p}
createIngrFactory() { return NYIngredientFactory() ; }
createIngrFactory()
createIngrFactory() createIngrFactory()
38
Overview of the workflow:PizzaCoHQ
:NYPizzaStore
: Customer
create
order(margherita)createPizza(margherita)
:NYIngredientFactorycreate
createDough()
createSauce()
:Margheritacreate
prepare
etc..bake
box
pizzadeliver(pizza)
39
“factory” with boiler
• A factory has 1x physical boiler that it needs to control from software.
• So, what may happen if there are two instances of the same physical Boiler (which you only have 1) in your app?
Boiler - empty = true- boiled = false+ Boiler() // constructor+ fill()+ boil()+ drain()
only fill when it is not empty:
fill() { if empty, empty = false }
Similarly:
boil() { if not empty, boil = false }
40
Singleton pattern
private Boiler() { empty=true; boiled=false ; }
private static Boiler uniqueInstance
public static Boiler getInstance { if uniqueInstance == null then uniqueInstance = new Boiler() return uniqueInstance}
Boiler ...-Boiler(...)- uniqueInstance : Boiler+ getInstance() : Boiler
Notice the lazy instantiation here...
41
Your app is multi-threadedBoiler - empty = true- boiled = false+ fill()+ boil()+ drain()
fill() { if (not empty) empty = false }
thread-1 doing fill() thread-2 doing fill()
not empty not empty
filling;setting empty to false filling;
setting empty to false
The boiler is filled 2x !!
(Java) we need to “synchronize” the operations.
How about the “getInstance” method; do we sync that too?
42
Another solution
empty = trueboiled = false...private Boiler() { }
private static Boiler uniqueInstance = new Boiler()
public static Boiler getInstance { return uniqueInstance }
Boiler ...-Boiler(...)-uniqueInstance : Boiler+ getInstance() : Boiler
// no need to sync this
// thread-safe by JVM
But you lose lazy instantiation. Else use “double-checked locking” see FF.