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

Post on 17-Dec-2015

231 Views

Category:

Documents

2 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

Whip

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.

top related