You are on page 1of 30

Decorator Pattern

Goals

• Learn how to decorate classes at runtime

• Give objects responsibilities at runtime, without


changing the code

• The power of extension at runtime rather than at


compile time
Coffee Shop Ordering
System
Coffee Shop Ordering
System

• Want to offer customers a variety of combination of


beverages (drinks) & condiments (milk, chocolate, …. )

• Cost depends on the combination in the order

• for example, cost(coffee with milk) = cost(coffee) +


cost(milk)
First Design
Abstract Class

cost () is abstract:
• description variable will be
• every subclass has its set in each subclass

own implementation • getDescription () returns


the description

each cost() method


compute the cost based
on the drink & the
condiments in the order
What is wrong?
• Too many subclasses (class explosion)

• maintenance headache

• Need to define the implementation for each subclass

• Change the price for one of the condiments

• update cost in all subclasses using it

• add new beverage (drink)

• think of all possible condiments that can be added to the drink


Second Design

• Instead of having all these subclasses to combine


beverages with all possible condiments

• Make one base class, that keeps instance variables for


condiments

• one variable for each possible condiment


Coffee Shop Design 2
Boolean variables

for each condiment

The override cost()

• computes the cost of the beverage

• adds the cost of all added condiments

• by calling the cost() in superclass


Cost method here will
compute the cost of all
added condiments
Compute cost
Supper class Concrete class
Compared to First Design

• Much better, very few classes compared to the class


explosion that we had in the first design

• but still it has problems


is the design flexible?
• adding new type of condiment

• requires modifying the code of the superclass

• include new methods; add set & get for the new condiment

• modifies the cost method to include new condiments in the calculation

• changing the price for one condiment

• requires modifying the existing code

• adding new beverages might inherit features (condiments) not suitable for them

• For example iceTea will inherit hasWhip

• how to deal with double/triple amount of a specific condiment (e.g., 2 milks) in the
same order
The inheritance problem

• inheriting behavior by subclassing

• the behavior is set statically at compile time

• Adding new functionality requires altering existing code


Design Principle
• Design principle

Classes should be open for extension,


but closed for modification

• The goal is to have classes that can be extended without


modifying their code

+ We will get a flexible design that allow adding new


functionalities easily to meet new requirements
How?
• How can we extend something without modifying the
code?

• extending behavior by composing

• the behavior is set dynamically at run time

• We saw this in the Observer pattern

How?

• How can we extend something without modifying the


code?

• We saw this in the Observer pattern

• We can add/remove Observers at runtime to the


Subject without modifying the code of the subject.
open-closed principle

• Don’t try to apply this rule on every part of the design

• this is a waste of time

• instead focus only on parts that are most likely to change


& apply the principle on them
What do we have so
far?
First Design

o n
l osi
xp
s e
la s
C
Coffee Shop Design 2
l a ss
p erc
e su e
t h riat s
i n p s e
l it y p ro las
i o na t ap ubc
n ct s no he s
d fu at i of t on
A d t h e a t i
o m f ic
o r s o di
f r m
f o
ib l e
l e x
Inf
Apply the Decorator Pattern

• Start with a beverage and decorate it with every condiment


added to it

• For example, if customer ordered DarkRoast with Mocha and


Whip, then

1.take the DarkRoast object

2.decorate it with Mocha object

3.decorate it with Whip object

4.call cost() and rely on delegation to add the condiments cost


Step-by-Step (1)

1. Create the base object (DarkRoast in the order example)

• DarkRoast inherits from the Beverage class

• so it gets cost() method to compute the cost of the


drink
cost()
DarkRoast
Step-by-Step (2)
2. create a Mocha object &
decorate (wrap) it around the
DarkRoast object

• Mocha is a decorator
cost()
cost()
DarkRoast

• decorator type mirrors the Mocha

the object it is decorating


(type = beverage)

• therefore it has the cost()


method
Step-by-Step (3)

3. create a Whip decorator, wrap


it around Mocha
cost()
cost()
cost()
• Mocha’s type is beverage
DarkRoast

Mocha
Whip

• so it has cost()
Step-by-Step (4)
4. Compute Cost

1. call cost() on outer most decorator


(Whip)

2. Whip calls cost on Mocha

3. Mocha call cost on DarkRoast

4. DarkRoast return its cost (0.99)

5. Mocha adds its cost (0.99 + 0.2)


and return new sum (1.19) to Whip

6. Whip adds its cost (0.99 + 0.2 +


0.1) and return 1.29
Key Points
• Decorators have the same type as the object they are
decorating

• Multiple decorators can be used to decorate an object

• Decorator can delegate work to be done by the objects it


decorates

• and it can add its own behavior before or after


delegation

• Objects can be decorated dynamically at runtime


Decorator Pattern Definition

attaches additional responsibilities to an object dynamically.


Decorators provide a flexible alternative to subclassing for
extending functionality
Decorator Class Diagram

The object that will


be decorated,

add behavior

to it dynamically

1. Decorator extends the same


super type as the object it is
going to decorate

2. also has reference to it. It


wraps the main component
(HAS-A) relation ship
A reference to
object to be
wrapped
Apply Decorator on the
Coffee Shop Ordering System
abstract component

concrete components
decorator

concrete decorators
Apply Decorator on the
Coffee Shop Ordering System
abstract component

concrete components
decorator

concrete decorators
Inheritance & Composition
• The decorator (CondimentDecorator) has two relation with the
component (Beverage)

• It is extending the Beverage (inheritance)

• this is to make sure that decorator is of the same type as


the component it is decorating (type matching)

• inheriting type, not the behavior

• Is is holding a reference to it (composition)

• to get the behavior

You might also like