Oliver Haase
Design Patterns
Decorator
Motivation
Your task is to program a coffee machine. The machine brews plain coffee, coffee with cream, sugar, sweetener, and
cinnamon. A plain coffee costs € 0,90, cream costs € 0,20, sugar € 0,10, sweetener € 0,10, cinnamon € 0,05. A plain
coffee has zero calories, a portion of cream adds 10 calories, a portion of sugar 20 calories, both sweetener and cinnamon
don't add any calories. All extras can be combined with each other.
Model the coffee entity (entities) such that it can be asked for its “configuration”, its price, and its calories.
Option 1 - Class Explosion
Coffee
CoffeeWith Cream
CoffeeWith Sugar
CoffeeWith Sweetener
CoffeeWith Cinammon
CoffeeWith CreamAnd
Sugar
CoffeeWith CreamAnd Sweetener
CoffeeWith CreamAnd Cinammon
CoffeeWith SugarAnd Cinammon
CoffeeWith
CoffeeWith SugarAnd Sweetener
CoffeeWith CoffeeWith CoffeeWith CoffeeWith
Option 1 - Class Explosion
What if extras can be added more than once (e.g. double cream, double sugar)?
Option 1 becomes not only messy, but impossible!
⇓
Option 2 - Attributed Coffee
getPrice() getCalories() toString()
cream: int sugar: int
sweetener: int cinammon: int
Coffee
0.9 + cream * 0.2 + sugar * 0.1
+ sweetener * 0.1 + cinammon * 0.05 cream * 10
+ sugar * 20
String result = "coffee"
Option 2 - Attributed Coffee
What if new extra is to be offered, e.g. sprinkles for € 0,30 and 5 calories?
Class Coffee needs to be modified.
⇓
Option 3 - Decorated Coffee
getPrice() getCalories() toString()
Coffee
0 getPrice()
getCalories() toString()
PlainCoffee
0.9
"Coffee"
getPrice() getCalories() toString()
CreamDecorator
getPrice() getCalories() toString() component
CoffeeDecorator
component.getPrice() component.getCalories() component.toString()
getPrice() getCalories() toString()
SugarDecorator
getPrice() getCalories() toString()
SweetenerDecorator
getPrice() getCalories() toString()
CinammonDecorator
Option 3 - Decorated Coffee
Sample coffee instantiation:
Coffee cupOfCoffee = new SugarDecorator(
new CreamDecorator(
new CreamDecorator(
new PlainCoffee())));
Resulting object diagram:
: PlainCoffee component
:CreamDecorator component
:CreamDecorator component
cupOfCoffee:
SugarDecorator
Option 3 - Decorated Coffee
Sample sequence diagram for a getPrice call:
: PlainCoffee :CreamDecorator
:CreamDecorator cupOfCoffee:
SugarDecorator getPrice()
getPrice()
getPrice()
getPrice() 0.9 1.3 1.1
1.4
Decorator - Structure
operation()
Component
operation()
ConreteComponent
operation()
ConcreteDecoratorB operation()
component Decorator
component.operation()
super.operation()
own additional functionality operation()
ConcreteDecoratorA Client
interface through which clients access (decorated) component
•maintains reference to decorated component
•defines same interface as Component
•delegates operation calls to decorated component
plain component that can be decorated
adds its own functionality to decorated component
Pros & Cons
Pros
‣
Simple, non-intrusive introduction of new decorators‣
objects can be (re-)decorated at runtime→ in contrast to option 1: class explosion
‣
different functionality for different object variants through polymorphism and chaining rather than conditionalstatements (option 2: object with attributes)
‣
object can be decorated with same decorator multiplePros & Cons
Cons
‣
multiply decorated object represented by multiple runtime objects→ runtime penalty, higher complexity, e.g., w.r.t. debugging
‣
component and its decorator are not the same object→ object identity gets lost
Application
Concurrency 1: Assume a non-threadsafe implementation of a mutable pair:
@NotThreadSafe
public class NotThreadSafePair<E1, E2> implements MutablePair<E1, E2> { private E1 left;
private E2 right;
public interface MutablePair<E1, E2> { E1 getLeft();
E2 getRight();
void setLeft(E1 left);
void setRight(E2 right);
void update(E1 left, E2 right);
}
Application
@Override public E1 getLeft() { return left; } @Override public E2 getRight() { return right; }
@Override public void setLeft(E1 left) { this.left = left; }
@Override public void setRight(E2 right) { this.right = right; } @Override public void update(E1 left, E2 right) {
this.left = left;
this.right = right;
}
@Override public String toString() {
return "NotThreadSafePair(" + left + ", " + right + ")";
} }
Application
Encapsulate not-threadsafe object with a threadsafe decorator:
@ThreadSafe
public class ThreadSafePair<E1, E2> implements MutablePair<E1, E2> { private MutablePair<E1, E2> delegate;
public ThreadSafePair(E1 left, E2 right) {
delegate = new NotThreadSafePair(left, right);
}
@Override public synchronized E1 getLeft() { return delegate.getLeft();
}
Application
@Override public synchronized void setLeft(E1 left) { delegate.setLeft(left);
}
@Override public synchronized void setRight(E2 right) { delegate.setRight(right);
}
@Override public synchronized void update(E1 left, E2 right) { delegate.update(left, right):
@Override public synchronized String toString() {
return "ThreadSafePair(" + left + ", " + right + ")";
} }
‣
Object confinement thru Java monitor pattern‣
see: Collections.synchronizedCollection(), Collections.synchronizedList(), etc.Application
Concurrency 2: Read-only decorators for collections
→ Collections.unmodifiable[Set|Map|Collection]:
‣
static factory methods that wrap backing collection‣
read operations are passed to backing collection‣
modifying operations returnUnsupportedOperationException
Related Patterns
Adapter: An adapter modifies the adapted object's interface;
a decorator adds functionality to an object without extending its interface.
Related Patterns
Proxy vs Decorator:
operation() Subject Client
operation() RealSubject
delegate.operation() operation()
delegate Proxy
operation() Component
operation()
ConreteComponent
operation()
ConcreteDecoratorB operation()
component Decorator
component.operation()
super.operation()
own additional functionality operation()
ConcreteDecoratorA Client
Related Patterns
Proxy vs Decorator:
‣
Similar structure: both involve a wrapper object that references the real subject‣
But:•
there is only one proxy, as opposed to many decorators for a single object•
Different purpose: proxy controls access to subject, while decorator changes component's functionalityFlyweight
Motivation
‣
Imagine a text processor that represents text documents consisting of pages, rows, words, and characters.‣
For homogeneity, it would be nice to treat the concepts of pages, rows, words, and characters similarly, in particularas objects.
‣
Problem: A book with 300 pages can easily contain 840 000 characters→ huge overhead if modeled as 840 000 objects!
Idea
‣
Divide object state into intrinsic and extrinsic state, such that there is only a small number of distinct objects withdifferent intrinsic states.
‣
Share these flyweight objects.‣
Feed flyweight objects with extrinsic state for operation invocations.Example
intrinsic state:
extrinsic state:
Character Flyweight Objects
character code (e.g. Unicode)
font, text style (bold, italics, regular), position
Applicability
Use the flyweight pattern only if all of the following apply:
‣
An application uses a large number of objects.‣
The memory consumption forbids instantiation of individual objects.‣
A big part of the object state can be moved into the context (can be made extrinsic).‣
Removal of the extrinsic state results in a small number of distinct objects.‣
The application does not depend on the object identity.Structure
declares operations that get fed with the extrinsic state
•implements the flyweight interface
•keeps intrinsic state of the shared object
•has reference to flyweight objects creates and maintains
flyweight objects
some implementations of Flyweight might not be shared - these may contain complete state
Interactions
‣
Intrinsic and extrinsic state must be clearly distinguishable.→ Flyweight objects store intrinsic state,
→ clients store or compute extrinsic state and supply it into flyweights' operations.
‣
Clients don't create flyweight objects directly. A flyweight factory makes sure flyweight objects are correctly shared.Consequences
‣
Reduced memory consumption comes at the cost ofincreased runtime, because client has to compute or access stored extrinsic state information, and pass it into flyweight objects.
‣
Memory saving depends on•
degree of reduction of objects;•
size of intrinsic state;•
whether extrinsic state is stored or calculated.Extrinsic State
‣
Applicability depends on how easily extrinsic state information can be identified and pulled out.‣
Benefit (in terms of memory consumption) depends on whether the amount of extrinsic state for all flyweightobjects is less than the original state information. This is the case, if extrinsic state
•
can be computed;•
is equal for groups of objects.Example → character flyweight objects:
intrinsic state: Character code (e.g. Unicode)
Related Patterns
‣
Flyweight often combined with →Composite pattern, to build hierarchy of objects with shared (flyweight) leaves.‣
→State and →Strategy objects are preferably implemented as flyweight objects.Closing Remarks
‣
Usually, patterns are intended to keep design simply, to reduce dependencies, to reduce number of classes, etc.→ simplicity, clarity, maintainability, & friends
‣
sometimes - though not always - at the expense of reduced efficiency‣
In contrast, flyweight pattern motivated by efficiency considerations→ relevance can be expected to decrease as main memory continuously gets cheaper
Composite
Motivation
“Imagine a GUI class library that consists of graphical elements, such as buttons, labels, textboxes, etc.
These graphical elements can be grouped and structured in graphical containers. A particular GUI might consist of deeply nested graphical containers and elements.
For practical purposes it would, however, be comfortable to be able to treat all graphical components, i.e. containers and elements, the same. E.g. both containers and elements should be able to be
asked to paint or scale themselves.”
Idea
Define a common supertype GraphicalComponent for both graphical containers and graphical elements; this supertype defines operations on the elements (paint, scale);
Structure - Example
Client
paint() scale()
GraphicalComponent
paint() scale()
add(GraphComp) components
Container paint()
scale() Label paint()
scale() Button paint()
scale() Textbox
for each comp in components for each comp in components comp.paint()
Sample Run-Time Object Graph
: Textbox
components :Container
components :Container
: Label
: Label : Button
Client
operation()
Component
operation()
add(Component) remove(Component) children
Composite operation()
Leaf
for each child in children child.operation()
General Structure
•declares interface for all objects of the nested structure•might provide default implementation
•might define and optionally implement an operation to access an object's parent
represents and defines
manipulates objects of composite structure thru component interface
•defines behavior of complex objects that can contain children
•stores references to children
• defines child management operations
Run-Time Object Graph, 2nd
: Leaf
children
:Composite
children
:Composite
: Leaf
: Leaf : Leaf
Applicability
Use the composite pattern if you want to
‣
model a whole-part-relationship;‣
be able to treat aggregate and atomic objects the same.Interactions
Clients use the Component interface to interact with objects in the structure.
‣
If the target is a primitive object, an operation call is handled directly.‣
If it is a composite, then the composite delegates the call to its children. It might do some extra work before or after the delegation.Consequences
‣
Composite patterns simplifies clients because they don'tneed to distinguish between primitive and complex objects
‣
New composite and leaf types can be added easily without modifications of the client code‣
Might be too general, if certain composite types should only contain certain leave types→ leads to explicit type checks at run-time.
Child Management Operations
Two basic options as to where to define operations to manage child objects (add, remove, getChild):
1. In Composite class, as seen
•
more type safety, i.e. client cannot try to add or remove children to primitive object•
less transparency, i.e. client needs to distinguish between primitive and composite objects when executing children- related operations2. In Component class, as described by GoF
•
possible default implementation: noop, exception•
complete transparency, i.e. client can treat primitive and composite objects the sameAlternative General Structure
Client operation()
add(Component) remove(Component) getChild(int)
Component
operation()
add(Component) remove(Component) children
Composite operation()
Leaf
for each child in children child.operation()
Operation to access an object's parent can simplify traversal of the composite structure.
Reference to Parent Object
‣
Where to be modeled?Component
‣
How to make sure all children of an object have that object as parent?set children's parent reference only in add and remove
operations of class Composite.
Related Patterns
‣
→ Chain of Responsibility: Reference to parent object often used to implement a chain of responsibility.‣
Flyweight can be used to share components. In that case, references to parent objects don't make sense.‣
→ Iterator can be used to access all components of a composite structure one by one.‣
→ Visitor can be used to traverse the structure and perform operations on individual elements.Related Patterns
‣
Decorator:•
can technically be regarded degenerated Composite with a decorator containing only one object, and a Compositecontaining many objects.
•
different purpose: Decorator adds functionality to same logical object, Composite models whole-part-relationship.Facade
Purpose
Provides a homogeneous interface to a set of interfaces of a subsystem. Simplifies usability of the subsystem.
Motivation
Motivation
Motivation
Problem: Client has to know a lot of classes / interfaces to simply watch a movie
→ many dependencies, tight coupling, hard to change home theater configuration without the need to change all clients
Motivation
Description
‣
Applicability: Use the facade pattern to•
hide a subsystem's internal complexity and provide asimplified interface to clients. Systems tend to grow over time, design patterns tend to increase the number of
classes. A facade shields clients from that complexity.
•
loosen coupling between the subsystem and clients. A facade removes dependencies from the actual subsystem classes.•
build multi-layered architectures, separated through one facade per layer as the entry point for the next higherStructure
•knows which subsystem classes are responsible for which requests•delegates client requests to respective subsystem objects
•implements subsystem functionality
•executes requests from the facade
•doesn’t know facade (doesn’t have reference to facade) → strict layering
Interactions
‣
Clients use the subsystem by sending requests to thefacade. The facade forwards the request to the responsible subsystem objects(s). One client request might well lead to several subsystem requests.
Even though the subsystem functionality is implemented in the subsystem, the facade might “fill in” some logic to
orchestrate the lower level requests.
‣
Clients that use the facade don't have to use the subsystem directly.Comparison of the
Structural Patterns
Circle of Structural Patterns
Circle of Structural Patterns
Adapter vs. Bridge
Commonalities:
‣
Level of indirection for the access to the actual object‣
Delegation from an interface that the actual object doesn't provideAdapter vs. Bridge
Differences:
‣
Purpose:•
Adapter intends to match an implementation with a (different) interface•
Bridge intends to separate implementation from abstract to enable both to evolve separately‣
Time of application:•
Adapter is employed rather later, i.e. when two existing types need to be brought together•
Bridge is employed early, to foresee separate evolution of abstraction and implementationComposite vs. Decorator
Commonalities:
‣
Structures of composite and decorator very similar (both use recursion to structure hierarchies of objects)‣
Decorator structure might be mistaken for degenerated composite structure→ composite builds tree, decorator builds chain of objects
Composite vs. Decorator
Differences are in their purpose:
‣
Decorator intends to add functionality to a type without changing it→ avoids exponential explosion of number of classes
‣
Composite intents to treat leaves and inner nodes (container objects) of an object hierarchy homogeneouslyProxy vs. Decorator
Commonalities:
‣
Similar structures, in both cases indirect access to actual object via upstream object‣
in both cases, upstream object maintains reference to actual object (subject) and delegates requests to itProxy vs. Decorator
Differences are in their purpose:
‣
Proxy is not about adding functionality, but about avoiding direct access to the subject, for varying reasons(protection, efficiency, transparent remote access)