Upload
others
View
2
Download
0
Embed Size (px)
Citation preview
Oliver Haase
Design Patterns
1
Decorator
Motivation
2
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
3
Coffee
CoffeeWithCream
CoffeeWithSugar
CoffeeWithSweetener
CoffeeWithCinammon
CoffeeWithCreamAndSugar
CoffeeWithCreamAndSweetener
CoffeeWithCreamAndCinammon
CoffeeWithSugarAndCinammon
CoffeeWithSweetenerAndCinammon
CoffeeWithSugarAndSweetener
CoffeeWithCreamAndSugarAndSweetener
CoffeeWithCreamAndSugarAndCinammon
CoffeeWithCreamAnd
SweetenerAndCinammon
CoffeeWithCreamAndSugarAnd
SweetenerAndCinammon
Option 1 - Class Explosion
4
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
5
getPrice()getCalories()toString()
cream: intsugar: intsweetener: intcinammon: int
Coffee
0.9 + cream * 0.2 + sugar * 0.1 + sweetener * 0.1 + cinammon * 0.05
cream * 10 + sugar * 20
String result = "coffee" for ( int i = 0; i < cream; i++) result += " cream";...
Option 2 - Attributed Coffee
6
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
7
getPrice()getCalories()toString()
Coffee
0
getPrice()getCalories()toString()
PlainCoffee
0.9
"Coffee"
getPrice()getCalories()toString()
CreamDecorator
getPrice()getCalories()toString()
componentCoffeeDecorator
component.getPrice()
component.getCalories()
component.toString()
super.getPrice() + 0.2
super.getCalories() + 10
super.toString() + "Cream"
getPrice()getCalories()toString()
SugarDecoratorgetPrice()getCalories()toString()
SweetenerDecoratorgetPrice()getCalories()toString()
CinammonDecorator
Option 3 - Decorated Coffee
8
Sample coffee instantiation:Coffee cupOfCoffee = new SugarDecorator( new CreamDecorator( new CreamDecorator( new PlainCoffee())));
Resulting object diagram:
: PlainCoffeecomponent
:CreamDecoratorcomponent
:CreamDecoratorcomponent
cupOfCoffee:SugarDecorator
Option 3 - Decorated Coffee
9
Sample sequence diagram for a getPrice call:
: PlainCoffee:CreamDecorator:CreamDecoratorcupOfCoffee:SugarDecorator
getPrice()getPrice()
getPrice()getPrice()
0.91.1
1.31.4
Decorator - Structure
10
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
11
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 conditional statements (option 2: object with attributes)
‣ object can be decorated with same decorator multiple times
Pros & Cons
12
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
ApplicationConcurrency 1: Assume a non-threadsafe implementation of a mutable pair:
13
@NotThreadSafepublic class NotThreadSafePair<E1, E2> implements MutablePair<E1, E2> { private E1 left; private E2 right;
public NotThreadSafePair(E1 left, E2 right) { this.left = left; this.right = 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
14
@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 + ")"; }}
ApplicationEncapsulate not-threadsafe object with a threadsafe decorator:
15
@ThreadSafepublic 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(); }
@Override public synchronized E2 getRight() { return delegate.getRight(); }
Application
16
@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 return UnsupportedOperationException
17
Related Patterns
18
Adapter: An adapter modifies the adapted object's interface; a decorator adds functionality to an object without extending its interface.
Related Patterns
19
Proxy vs Decorator:
operation()SubjectClient
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
20
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 functionality
Flyweight
21
Motivation
22
‣ 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 particular as 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 with different intrinsic states.
‣ Share these flyweight objects.
‣ Feed flyweight objects with extrinsic state for operation invocations.
23
Example
intrinsic state: extrinsic state:
24
Character Flyweight Objects
character code (e.g. Unicode)font, text style (bold, italics, regular), position
ApplicabilityUse 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.
25
Structure
26
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•keeps or computes objects’ extrinsic state
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.
27
Consequences
‣ Reduced memory consumption comes at the cost of increased 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.
28
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 flyweight objects 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)extrinsic state: font, text style, positionClient doesn't have to store font and text style per flyweight object, but stores these attributes per bigger chunks of text.
29
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.
30
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
31
Composite
32
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.”
33
Idea
Define a common supertype GraphicalComponent for both graphical containers and graphical elements; this supertype defines operations on the elements (paint, scale);
34
Structure - Example
35
Clientpaint()scale()
GraphicalComponent
paint()scale()add(GraphComp)remove(GraphComp)getChild(int)
componentsContainer
paint()scale()
Labelpaint()scale()
Buttonpaint()scale()
Textbox
for each comp in components comp.scale()
for each comp in components comp.paint()
Sample Run-Time Object Graph
36
: Textbox
components
:Container
components
:Container
: Label
: Label : Button
Clientoperation()
Component
operation()add(Component)remove(Component)getChild(int)
childrenComposite
operation()Leaf
for each child in children child.operation()
General Structure
37
•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 behavior of primitive objects
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
38
: 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.
39
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.
40
Consequences
‣ Composite patterns simplifies clients because they don't need 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.
41
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 operations
2. In Component class, as described by GoF
• possible default implementation: noop, exception
• complete transparency, i.e. client can treat primitive and composite objects the same
42
Alternative General Structure
43
Client operation()add(Component)remove(Component)getChild(int)
Component
operation()add(Component)remove(Component)getChild(int)
childrenComposite
operation()Leaf
for each child in children child.operation()
Operation to access an object's parent can simplify traversal of the composite structure.
44
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.
45
Related Patterns
‣ Decorator: • can technically be regarded degenerated Composite with a
decorator containing only one object, and a Composite containing many objects.
• different purpose: Decorator adds functionality to same logical object, Composite models whole-part-relationship.
46
Facade
47
Purpose
48
Provides a homogeneous interface to a set of interfaces of a subsystem. Simplifies usability of the subsystem.
Motivation
49
Motivation
50
Motivation
51
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
52
Description
53
‣Applicability: Use the facade pattern to• hide a subsystem's internal complexity and provide a
simplified 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 higher layer.
Structure
54
•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 the facade. 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.
55
Comparison of the Structural Patterns
56
Circle of Structural Patterns
57
58
Circle of Structural Patterns
Adapter vs. Bridge
59
Commonalities:
‣ Level of indirection for the access to the actual object
‣ Delegation from an interface that the actual object doesn't provide
Adapter vs. Bridge
60
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 implementation
Composite 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
61
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 homogeneously
62
Proxy 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 it
63
Proxy 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)
‣With proxy pattern, subject implements key functionality, with decorator pattern, functionality is split across levels of indirection.
64