14.09.2015 Views

Decorator Pattern

Printable Version

Printable Version

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>Decorator</strong> <strong>Pattern</strong><br />

Steven R. Bagley


Introduction<br />

• <strong>Decorator</strong> <strong>Pattern</strong><br />

• Inheritance vs. Composition


Tricolour Coffee Bar<br />

• Fast-growing coffee chain<br />

• Started by a computer<br />

scientist<br />

• Wants a fully OO based<br />

ordering system


Drinks<br />

• Coffees<br />

• House Blend<br />

• Dark Roast<br />

• Decaf<br />

• Espresso<br />

• Toppings<br />

• Mocha<br />

• Steamed Milk<br />

• Soy<br />

• Whip


Where to Begin?<br />

• Assume it serves only drinks<br />

• Abstract base class of Drink<br />

• Coffee Types inherit from Drink<br />

• Methods for<br />

• Description<br />

• Cost


Drink<br />

description<br />

GetDescription<br />

Cost<br />

HouseBlend<br />

cost()<br />

DarkRoast<br />

cost()<br />

Decaf<br />

cost()<br />

Espresso<br />

cost()


class CBeverage<br />

{<br />

public:<br />

string GetDescription();<br />

virtual double cost() = 0;<br />

protected:<br />

string m_description;<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


class CBeverage<br />

{<br />

public:<br />

string GetDescription();<br />

virtual double cost() = 0;<br />

protected:<br />

string m_description;<br />

};<br />

same as abstract<br />

in Java<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


string CBeverage::GetDescription()<br />

{<br />

return m_description;<br />

}


class CHouseBlend : public CBeverage<br />

{<br />

public:<br />

CHouseBlend();<br />

virtual double cost();<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


class CHouseBlend : public CBeverage<br />

{<br />

public:<br />

CHouseBlend();<br />

virtual double cost();<br />

};<br />

provide an<br />

implemenation of the<br />

abstract method<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


CHouseBlend::CHouseBlend()<br />

{<br />

m_description = “House Blend”;<br />

}<br />

double CHouseBlend::cost()<br />

{<br />

return 0.89;<br />

}


Toppings<br />

• What about the Toppings?<br />

• Inherit the various types?<br />

• EspressoWithMocha<br />

• DarkRoastWithSteamedMilk<br />

• Implementation as before


CHouseBlendWithMocha::<br />

CHouseBlendWithMocha()<br />

{<br />

m_description = “House Blend with Mocha”;<br />

}<br />

double CHouseBlendWithMocha::cost()<br />

{<br />

return 0.89 + 0.20;<br />

}


Class Explosion<br />

• Where did all these classes come from?<br />

• 16 combination of toppings<br />

• 4 drinks<br />

• 64 different classes to implement<br />

• Maintenance nightmare!<br />

• What if the cost of Mocha topping goes up?


Design Principles<br />

• Does it break our Design Principles?<br />

• Encapsulate what varies<br />

• Favour composition over inheritance<br />

• Program to interfaces, not<br />

implementation<br />

Always a good idea to measure your design up against these design principles. Try and<br />

justify why you are breaking one... It is almost certainly a bad idea


Encapsulate what varies<br />

• Fail here — Big Time!<br />

• Nothing is encapsulated<br />

• DarkRoastWithMocha<br />

• HouseBlendWithMocha<br />

• HouseBlendWithSteamedMilk<br />

• No Code Reuse…


Favour Composition<br />

• Inheritance 64 — Composition 0<br />

• No composed classes<br />

• Definite fail here…


Interface<br />

• We meet this<br />

• Beverage interface<br />

• No need to use concrete classes


Square One<br />

• Unworkable design<br />

• A new topping means 64 new classes<br />

• A new coffee means 16 new classes<br />

• Back to the drawing board<br />

• Need to do this without using so many<br />

classes


Attempt Two<br />

• Apply first design principle<br />

• Put the toppings into Beverage as instance<br />

variables<br />

• Encapsulates what varies<br />

• Sub-classes call Beverage’s cost() to get<br />

cost of toppings


Drink<br />

description<br />

milk<br />

soy<br />

mocha<br />

whip<br />

GetDescription<br />

Cost<br />

HasMilk<br />

SetMilk<br />

HasSoy<br />

SetSoy<br />

HasMocha<br />

SetMocha<br />

HasWhip<br />

SetWhip<br />

HouseBlend<br />

cost()<br />

DarkRoast<br />

cost()<br />

Decaf<br />

cost()<br />

Espresso<br />

cost()


int CDrink::Cost()<br />

{<br />

int cost = 0;<br />

if(m_hasMocha)<br />

cost += 0.20;<br />

if(m_hasMilk)<br />

cost += 0.10;<br />

…<br />

return cost;<br />

}


int CDarkRoast::Cost()<br />

{<br />

return CDrink::Cost() + 1.50;<br />

}


WooHoo<br />

• Only five classes<br />

• Code reuse<br />

• No Ducks!<br />

• This must be the way to do it!


Or maybe not…<br />

• Price changes to toppings mean we have to<br />

edit existing code (we could break it)<br />

• New toppings mean new methods and<br />

editing the cost method (recompile<br />

everything)<br />

• What about drinks without toppings?<br />

• Or double toppings?


Inheritance<br />

• Inheritance is powerful<br />

• Doesn’t always lead to flexible designs<br />

• Can ‘inherit’ behaviour at runtime via<br />

composition


Inherit or Compose<br />

• Behaviour is fixed statically at compile time<br />

• Composition can extend at runtime<br />

• Composition allows us to add new<br />

responsibilities to objects without touching<br />

the superclass<br />

• New functionality by writing new code, not<br />

editing old (and working) code


Design Principle<br />

“Classes should be open for extension,<br />

but closed for modification”


Open-Closed Principle<br />

• Classes should be open for extension<br />

• Feel free to extend our classes with any<br />

new behaviour you like, but…<br />

• closed for modification<br />

• Sorry, but our code is fixed and bug free<br />

you can’t change it<br />

• Inheritance means code-change


Open-closed Design<br />

• How do you design something to be open<br />

for extension but closed to modification?<br />

• Lots of OO techniques<br />

• Keep things loosely coupled


Tightly coupled<br />

example<br />

• Suppose we have a class that prints stuff<br />

• Outputs the data to the standard output in<br />

the usual way<br />

System.out.println(“…”);<br />

printf(“…”);<br />

• Coupled tightly to the output classes<br />

• Can’t work with out them, can’t output to<br />

anything else


public class TightCouple<br />

{<br />

public DebugOutput()<br />

{<br />

System.out.println(“Debug output”);<br />

…<br />

System.out.println(“End Debug”);<br />

}<br />

}


Make it loosely coupled<br />

• Class is tied to outputting to standard<br />

output<br />

• Via the System.out class<br />

• If we want to output to a file (or<br />

elsewhere) then we’d need to rewrite code<br />

• However, we can rewrite it to be loosely<br />

coupled


public class LooseCouple<br />

{<br />

public DebugOutput(PrintStream output)<br />

{<br />

output.println(“Debug output”);<br />

…<br />

output.println(“End Debug”);<br />

}<br />

}


obj.DebugOutput(System.out);<br />

Call it by passing it a reference or pointer to the object<br />

it uses to print things out<br />

obj no longer tied to any specific output


<strong>Decorator</strong> <strong>Pattern</strong><br />

• ‘We need to sell some coffee here’<br />

• Can use the <strong>Decorator</strong> <strong>Pattern</strong> to<br />

solve this design<br />

• Uses composition rather than inheritance


Dark


MochaDark


Whip MochaDark


Whip MochaDark<br />

All Objects are of the<br />

same type, Drink


All Objects are of the<br />

same type, Drink<br />

Whip MochaDark<br />

Mocha and Whip are ‘<strong>Decorator</strong>s’, they<br />

take an object of type Drink and wrap it up


<strong>Decorator</strong><br />

• Start with a DarkRoast object<br />

• Customer wants Mocha, wrap a Mocha<br />

object around DarkRoast<br />

• Also want Whip, wrap a Whip object<br />

around Mocha<br />

• Both decorators and concrete classes share<br />

same type, Drink


Coffee <strong>Decorator</strong>s<br />

• How it works<br />

• Call cost() on the decorated object<br />

• <strong>Decorator</strong> calls cost() on the object it<br />

decorates and adjusts the price<br />

• <strong>Decorator</strong>s can decorate <strong>Decorator</strong>s


Drink<br />

description<br />

GetDescription<br />

Cost<br />

HouseBlend<br />

cost()<br />

DarkRoast<br />

cost()<br />

Decaf<br />

cost()<br />

Espresso<br />

cost()<br />

Condiment<strong>Decorator</strong><br />

Drink<br />

Milk<br />

cost<br />

GetDescription<br />

Mocha<br />

cost<br />

GetDescription<br />

Soy<br />

cost<br />

GetDescription<br />

Whip<br />

cost<br />

GetDescription


class CBeverage<br />

{<br />

public:<br />

virtual string GetDescription();<br />

virtual double Cost() = 0;<br />

protected:<br />

string m_description;<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


string CBeverage::GetDescription()<br />

{<br />

return m_description;<br />

}


string CBeverage::GetDescription()<br />

{<br />

return m_description;<br />

}<br />

return a copy of<br />

the description


class CHouseBlend : CBeverage<br />

{<br />

public:<br />

CHouseBlend();<br />

virtual double Cost();<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


class CHouseBlend : CBeverage<br />

{<br />

public:<br />

CHouseBlend();<br />

virtual double Cost();<br />

};<br />

provide an<br />

implemenation of the<br />

abstract method<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


string CHouseBlend::CHouseBlend()<br />

{<br />

m_description = “House Blend”;<br />

}<br />

double CHouseBlend::Cost()<br />

{<br />

return 0.89;<br />

}


class CCondiment<strong>Decorator</strong> : public CBeverage<br />

{<br />

public:<br />

virtual string GetDescription() = 0;<br />

virtual double Cost() = 0;<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


class CMocha : public CCondiment<strong>Decorator</strong><br />

{<br />

public:<br />

! CMocha(CBeverage *beverage);<br />

! ~CMocha();<br />

!<br />

! virtual string GetDescription();<br />

! virtual double Cost();<br />

private:<br />

! CBeverage *m_beverage;<br />

};<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


CMocha::CMocha(CBeverage *beverage)<br />

{<br />

! m_beverage = beverage;<br />

}<br />

CMocha::~CMocha()<br />

{<br />

! delete m_beverage;<br />

}<br />

string CMocha::GetDescription()<br />

{<br />

! return m_beverage->GetDescription() + ", Mocha";<br />

}<br />

double CMocha::Cost()<br />

{<br />

! return m_beverage->Cost() + 0.20;<br />

}<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


! CBeverage *beverage2 = new CDarkRoast();<br />

! beverage2 = new CMocha(beverage2);<br />

! beverage2 = new CMocha(beverage2);<br />

Abstract class -- can’t instantiate directly<br />

but we can instantiate subclasses


<strong>Decorator</strong> <strong>Pattern</strong><br />

• The <strong>Decorator</strong> <strong>Pattern</strong> attaches additional<br />

responsibilities to an object dynamically.<br />

<strong>Decorator</strong>s provide a flexible alternative to<br />

subclassing for extending functionality<br />

• Black-box reuse<br />

• Classes implementation unchanged


<strong>Decorator</strong><br />

Components<br />

• Abstract Component interface<br />

• Some Concrete Components that implement<br />

interface<br />

• Some <strong>Decorator</strong>s that also implement the<br />

interface


<strong>Decorator</strong><br />

• Each <strong>Decorator</strong> has-a component that it<br />

wraps up (instance variable containing a<br />

reference)<br />

• <strong>Decorator</strong>s add functionality<br />

• New Methods (unusual)<br />

• Additional code before or after existing<br />

methods


Decorated Inheritance<br />

• Inheritance is used to get type<br />

compatibility, not behaviour<br />

• Object being decorated is never changed<br />

• If it is bug-free it stays bug free<br />

• If we inherit, we are effectively altering the<br />

code for that object which can introduce<br />

bugs


Inheritance means<br />

rewrite<br />

• Suppose a class has two methods<br />

• foo()<br />

• bar()<br />

• foo() uses bar() to do some of its work<br />

• Inherit a new class which replaces bar()’s<br />

behaviour<br />

• foo() does not work correctly anymore


Do not modify<br />

• On the other hand if we decorate the class<br />

to replace bar()<br />

• foo() still works (it calls the original bar<br />

())<br />

• New bar() still called via decorator<br />

• Inherit for type, not behaviour


Deja vu<br />

• Anyone getting the impression they’ve seen<br />

this before?


Java File I/O<br />

• Java’s File I/O is based around the<br />

<strong>Decorator</strong> <strong>Pattern</strong><br />

• Abstract root InputStream<br />

• Concrete base types FileInputStream,<br />

StringBufferStream<br />

• Stream <strong>Decorator</strong>s (FilterInputStream)<br />

BufferedInputStream,<br />

LineNumberInputStream


InputStream in = new BufferedInputStream(new<br />

FileInputStream(“test.txt”));<br />

in.read();<br />

Only have to implement bufferedInputStream once, not for every input stream type


public class LowerCaseInputStream extends FilterInputStream<br />

{<br />

! public LowerCaseInputStream(InputStream in)<br />

{<br />

! ! super(in);<br />

! }<br />

! public int read() throws IOException<br />

{<br />

! ! int c = super.read();<br />

! ! return (c == -1 ? c : Character.toLowerCase((char)c));<br />

! }<br />

! !<br />

! public int read(byte[] b, int offset, int len) throws …<br />

{<br />

! ! int result = super.read(b, offset, len);<br />

! ! for (int i = offset; i < offset+result; i++)<br />

{<br />

! ! ! b[i] = (byte)Character.toLowerCase((char)b[i]);<br />

! ! }<br />

! ! return result;<br />

! }<br />

}<br />

Easy to implement new input stream filters

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

Saved successfully!

Ooh no, something went wrong!