• Keine Ergebnisse gefunden

Design Patterns

N/A
N/A
Protected

Academic year: 2022

Aktie "Design Patterns"

Copied!
64
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Oliver Haase

Design Patterns

Decorator

(2)

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.

(3)

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

(4)

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!

(5)

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"

(6)

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.

(7)

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

(8)

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

(9)

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

(10)

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

(11)

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 conditional

statements (option 2: object with attributes)

object can be decorated with same decorator multiple

(12)

Pros & 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

(13)

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);

}

(14)

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 + ")";

} }

(15)

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();

}

(16)

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.

(17)

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

(18)

Related Patterns

Adapter: An adapter modifies the adapted object's interface;

a decorator adds functionality to an object without extending its interface.

(19)

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

(20)

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 functionality

(21)

Flyweight

(22)

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 particular

as objects.

Problem: A book with 300 pages can easily contain 840 000 characters

→ huge overhead if modeled as 840 000 objects!

(23)

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.

(24)

Example

intrinsic state:

extrinsic state:

Character Flyweight Objects

character code (e.g. Unicode)

font, text style (bold, italics, regular), position

(25)

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.

(26)

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

(27)

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.

(28)

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.

(29)

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)

(30)

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.

(31)

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

(32)

Composite

(33)

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.”

(34)

Idea

Define a common supertype GraphicalComponent for both graphical containers and graphical elements; this supertype defines operations on the elements (paint, scale);

(35)

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()

(36)

Sample Run-Time Object Graph

: Textbox

components :Container

components :Container

: Label

: Label : Button

(37)

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

(38)

Run-Time Object Graph, 2nd

: Leaf

children

:Composite

children

:Composite

: Leaf

: Leaf : Leaf

(39)

Applicability

Use the composite pattern if you want to

model a whole-part-relationship;

be able to treat aggregate and atomic objects the same.

(40)

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.

(41)

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.

(42)

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

(43)

Alternative 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()

(44)

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.

(45)

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.

(46)

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.

(47)

Facade

(48)

Purpose

Provides a homogeneous interface to a set of interfaces of a subsystem. Simplifies usability of the subsystem.

(49)

Motivation

(50)

Motivation

(51)

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

(52)

Motivation

(53)

Description

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

(54)

Structure

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

(55)

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.

(56)

Comparison of the

Structural Patterns

(57)

Circle of Structural Patterns

(58)

Circle of Structural Patterns

(59)

Adapter vs. Bridge

Commonalities:

Level of indirection for the access to the actual object

Delegation from an interface that the actual object doesn't provide

(60)

Adapter 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 implementation

(61)

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

(62)

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

(63)

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

(64)

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.

Referenzen

ÄHNLICHE DOKUMENTE

public class ConcreteDocFactory implements DocumentFactory { private static final ConcreteDocFactory instance =.

Assume, a framework defines a base class (interface) that the specific custom class has to extend (implement), in order to be used in place of the base class (interface).. The

‣ Remark II: If the command object contains only one operation, function pointers can be considered more lightweight and thus more

public static final class Memento { private final int state;. private Memento(int state) { this.state

‣ The class TCPConnection maintains a state object that represents the current state of the TCP connection.. ‣ The class TCPConnection delegates all state-specific requests

I Similar structures, in both cases indirect access to actual object via upstream object. I in both cases, upstream object maintains reference to actual object (subject) and

I Divide object state into intrinsic and extrinsic state, such that there is only a small number of distinct objects with different intrinsic states.. I Share these

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