Decorator Pattern
Printable Version
Printable Version
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