design pattern 1 [eric freeman & elisabeth freeman 1 – 5 ]

42
Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

Upload: suzanna-sophie-marshall

Post on 17-Dec-2015

231 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

Design Pattern 1[Eric Freeman & Elisabeth Freeman 1 – 5 ]

Page 2: 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()

Page 3: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 4: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 5: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 6: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 7: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 8: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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”

Page 9: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

9

A bit unortodox...

Page 10: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 11: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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)

Page 12: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

12

Observer pattern

Observerupdate(data)

SubjectaddObserver (o)removeObserver(o)notifyObservers()setState(...)

observerssubject

*0..1

subjectclient obs1 obs2

setState(..) notifyObservers(..)

update(data)update(data)

Page 13: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 14: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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 ….

Page 15: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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...

Page 16: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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()

Page 17: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

17

Maintenance??

Page 18: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 19: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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 : Darkroast

Whip

d : Darkroast

Page 20: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

20

Decorator pattern

Componentoperation(…)

Concrete Componentoperation(…)

Decoratoroperation(…)

ConcreteDecorator Aoperation(…)

ConcreteDecorator Boperation(…)

Client <<use>>

Beverage

Esperesso, Darkroast,Decaf

Milk, Cocoa, Whip

Condiment

Page 21: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 22: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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)

Page 23: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 24: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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}

Page 25: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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…

Page 26: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 27: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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)

Page 28: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 29: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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}

Page 30: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 31: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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 …

Page 32: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.

Page 33: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 34: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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

Page 35: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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 …}

Page 36: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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)

Page 37: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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()

Page 38: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

38

Overview of the workflow:PizzaCoHQ

:NYPizzaStore

: Customer

create

order(margherita)createPizza(margherita)

:NYIngredientFactorycreate

createDough()

createSauce()

:Margheritacreate

prepare

etc..bake

box

pizzadeliver(pizza)

Page 39: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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 }

Page 40: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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...

Page 41: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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?

Page 42: Design Pattern 1 [Eric Freeman & Elisabeth Freeman 1 – 5 ]

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.