• Keine Ergebnisse gefunden

Design Patterns

N/A
N/A
Protected

Academic year: 2022

Aktie "Design Patterns"

Copied!
55
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Oliver Haase

Design Patterns

Command

(2)

Description

2

Purpose: Encapsulate a command as an object. Allows to dynamically configure an invoker with a command object.

Invoker can invoke command without knowing its specifics, nor the receiver of the command.

Also Known As: Action, Transaction

(3)

Motivation & Remarks

‣ Most widely known application of the command pattern:

event listeners for GUI programming

Remark: Command pattern is the object-oriented

counterpart of function pointers in procedural languages

Remark II: If the command object contains only one

operation, function pointers can be considered more

lightweight and thus more appropriate

(4)

Remarks

Remark III: In C#, a function pointer is called a delegate.

Example:

4 // C#

public delegate boolean FUNC(object x, object y);

public boolean Compare (object x, object y) { ... } public boolean contains (object x , object[ ] field) { CMP_Func myFuncObj = new FUNC(Compare) ;

foreach ( object obj in field ) f if ( myFuncObj ( obj , x ) ) return true;

}

return false;

}

(5)

Motivation

‣ In Java Swing, when a JButton is pressed, the button

invokes the actionPerformed method of a pre-registered

ActionListener command object.

The ActionListener interface contains only the

actionPerformed method.

an ActionListener command object can be registered

using the JButton 's addActionListener method.

(6)

Another Remark

Remark IV: In Java, an anonymous class might be a good

choice to avoid the fully-fledged declaration of a class for a single method that is called only once:

6

JButton submitButton = new JButton ( "submit" ) ;

submitButton.addActionListener (new ActionListener( ) { public void actionPerformed( ActionEvent e) {

// do whatever is needed to submit }

} ) ;

(7)

Sample Structure

MyApplication

actionPerformed() ActionListener

action() MyModel

model.action() actionPerformed()

model

MyActionListener actionListener

JButton

(8)

Applicability

Use the command pattern if you want to

‣ dynamically configure an object with an action;

‣ support →undo functionality;

‣ keep a log of the executed actions so they can be redone in case of a crash;

‣ model a complex transaction;

→ in this case the encapsulation of the transaction in a command object reduces code dependencies.

8

(9)

General Structure

Client

execute() Command

action() Receiver

receiver.action() execute()

receiver

ConcreteCommand command

Invoker

• defines action to be performed

• defines receiver

declares interface for command invocation invokes the command

through the Command interface.

creates a concrete command object and passes the receiver

into it.

knows how to perform

the action.

(10)

Interactions

‣ Client creates concrete command object and determines the receiver

‣ Invoker stores the concrete command object

‣ Invoker executes a request by invoking the command object's execute operation

‣ If a command can be undone, the command object stores the receiver's original state so it can be restored later

‣ The concrete command object implements the request by calling some operation at the receiver object

10

(11)

Undo & Redo

‣ provide an additional undo operation in the Command

interface

‣ Invoker stores the concrete command object

in each ConcreteCommand class, store the state before execution of the execute operation; this may include:

• all parameters for the action performed by the receiver

• all state information of the receiver that might change due to the execution of the action ( → memento pattern)

‣ receiver must allow command object to restore receiver's original state

How to implement chains of undos and redos:

(12)

Undo & Redo

‣ Client stores all executed commands in a command history

→ depth of command history determines number of possible undo/redo steps

‣ command objects are copied before insertion into command history

12

How to implement chains of undos and redos:

(13)

Consequences

‣ Command pattern decouples the entity that invokes a request ( Invoker ) from the one that knows how to implement it ( Command )

‣ Command objects can be manipulated (configured) and extended as any other object.

‣ Command objects can be composed to build macro

objects. For this purpose, the composite pattern can be employed.

‣ New command objects can easily be created without modifying existing classes.

‣ Command objects can be replaced at runtime

→ useful, e.g., for context-sensitive menus

(14)

Related Patterns

‣ The Composite pattern can be used to build macro

commands (in which case the MacroCommand class doesn't point to a receiver object)

‣ If a command needs to store the receiver's state (undo), it can use the →Memento pattern

‣ If a command object needs to be copied before being

inserted into the command history, then it behaves like a Prototype

14

(15)

Iterator

(16)

Purpose

16

Give sequential access to the individual elements of a complex object structure without revealing its internals.

Also known as: Cursor

(17)

Sample Structure

Client

iterator() List

iterator() ArrayList

hasNext() next()

Iterator

hasNext() next()

ArrayListIterator

iterator() Vector

hasNext() next()

VectorIterator

(18)

Consequences / Benefits

Using an iterator, a structure can be traversed

‣ multiple times at the same time;

‣ in different orders without changing the structure’s interface.

18

(19)

General Structure

Client

createIterator() Aggregate

return new

ConcreteIterator() createIterator()

ConcreteAggregate

hasNext() next()

Iterator

hasNext() next()

ConcreteIterator

• implements Iterator interface defines operation to

create iterator instance

creates concrete iterator

defines interface to access and traverse elements of an

aggregate uses concrete aggregate through Aggregate and

concrete iterator through Iterator interface.

(20)

Privileged Access

‣ friend class concept (C++): make iterator friend of aggregate

‣ non-static member classes (Java): Beware, publication of an inner class instance implicitly also publishes outer instance.

20

Iterators usually have privileged access to the elements of an

aggregate. This can be achieved through

(21)

Robust Iterators

‣ It can be dangerous to modify an aggregate while it is being traversed

robust iterators don't get affected

→ possible techniques:

• iterator works on deep copy of aggregate

→ potentially out-dated snapshot

• iterator is registered with aggregate, aggregate notifies

iterators about modifications

(22)

Java Iterator Interface

‣ the Java collection framework uses the Iterator pattern

‣ each implementation of Collection<E> implements the interface Iterable<E>:

22 interface Iterable<E> {

Iterator<E> iterator();

}

interface Iterator<E> { boolean hasNext();

E next();

void remove();

}

‣ custom aggregate classes can and should also implement

Iterable and provide appropriate iterators.

(23)

Java Iterator Interface

‣ sample usage:

for ( Iterator<ElementType> it = aggregate.iterator(); it.hasNext(); ) { iterator.next().use();

}

‣ or, as a for-each loop:

for ( ElementType element : aggregate ) { element.use();

}

‣ the for-each loop is internally translated into the above

for loop.

(24)

Java Iterator Interface

24

What’s wrong with the following code snipplet?

public static void useVector(Vector<T> vector) { for (T element : vector)

element.use();

}

Not threadsafe, because vector might get modified while being iterated! - This is true even though Vector is a

synchronized collection.

Concurrent modification of underlying collection may result in ConcurrentModificationException

→ iterator fail-fast, but on a best effort basis, i.e. applications

must not rely on it

(25)

Java Iterator Interface

Possible Solutions:

1. copy vector, iterate on the copy → performance cost, iterates over a potentially outdated snapshot

2. client-side locking:

public static void useVector(Vector<T> vector) { synchronized ( vector ) {

for (T element: vector) element.use();

} }

→ prevents other threads from modifying vector

during iteration, but also from accessing vector at all!

(26)

Java Iterator Interface

What’s wrong with the following code snipplet?

26

public static void filterVector(Vector<T> vector) { for (T element : vector)

if ( !element.isValid() ) vector.remove(element);

}

Same thread modifies vector while being iterated

→ ConcurrentModificationException!

(27)

Java Iterator Interface

Solution: modify vector through iterator, not directly.

public static void filterVector(Vector<T> vector) {

for (Iterator<T> it = vector.iterator(); it.hasNext(); ) if ( ! it.next().isValid() )

it.remove();

}

(28)

Hidden Iterators

28

What’s wrong with the following class?

public class HiddenIterator {

private final Set<Integer> set = new HashSet<Integer>();

public synchronized void add(Integer i) { set.add(i); }

public synchronized void remove(Integer i) { set.remove(); }

public void addTenThings() { Random r = new Random();

for ( int i = 0; i < 10; i++ ) add(r.nextInt());

System.out.println(“Added ten elements to “ + set);

} }

set.toString() contains a hidden iterator

→ not threadsafe!

(29)

Hidden Iterators

The following methods that operate on collections all contain hidden iterators:

toString()

hashCode()

equals()

‣ containsAll()

‣ removeAll()

‣ retainAll()

‣ constructors that take collections as arguments

called if collection is used as key or

element of another collection

(30)

Iterating Concurrent Collections

S ince Java 5.0, Collection framework contains several

concurrent collections that allow for concurrent access while still being threadsafe, including:

‣ ConcurrentHashMap ( → lock striping)

‣ CopyOnWriteArrayList (→ new copy for each modification)

‣ ConcurrentLinkedQueue ( → lock striping)

30

Iterators on concurrent collections are weakly consistent

→ traverse elements as they were at time of iterator construction

→ cannot throw ConcurrentModificationException !

(31)

Related Patterns

‣ Iterators are often used for recursive structures such as Composita

‣ For iterator creation, aggregate defines a factory method

‣ Iterators can use the →Memento pattern to store the state

of an iteration.

(32)

Visitor

32

(33)

Purpose

Separate algorithm from the object structure upon which it

operates.

(34)

Motivation

Consider the following list structure:

34 Client

sum(): int

chars(): String List

sum(): int

chars(): String NIL

tail

Element

sum(): int chars: String head: int

IntElement

sum(): int chars: String head: char

CharElement

[compare: J. Bloch, Effective Java, 2nd Edition, item 43: Return

empty arrays or collections, not null]

(35)

Motivation

public interface List { int sum();

String chars();

}

@Immutable // even stateless

public class Nil implements List {

@Override public String chars() { return ""; } @Override public int sum() { return 0; }

}

public abstract class Element implements List { protected final List tail;

protected Element(List tail) { this.tail = tail; } }

(36)

Motivation

36

@Immutable

public class IntElement extends Element { private final int head;

public IntElement(int number, List tail) { super(tail);

head = number;

}

@Override public String chars() { return tail.chars(); } @Override public int sum() { return head + tail.sum(); } }

@Immutable

public class CharElement extends Element { private char head;

public CharElement(char character, List tail) { super(tail);

head = character;

}

@Override public String chars() { return head + tail.chars(); } @Override public int sum() { return tail.sum(); }

}

(37)

Motivation

public class Client {

public static void main(String[] args) { List l =

new IntElement(4,

new CharElement('b', new CharElement('a', new IntElement(3, new Nil()))));

System.out.println("Sum: " + l.sum());

System.out.println("Characters: " + l.chars());

} }

Sample Usage:

(38)

Motivation

Problem: Introduction of a new operation, e.g.

38

requires modification of

List interface

IntElement class

‣ CharElement class

Generally, of all classes of the object structure.

boolean contains (char c);

(39)

Key Idea

Add an accept(Visitor) method to each element type.

‣ In each specific visitor, provide one visit(ElementType)

operation per ElementType .

‣ To visit an element, visitor calls element’s accept operation which in turn calls the visitor’s appropriate

visit(ElementType) operation.

Idea: Separate operations ( sum() , chars() , …) into visitor

classes that traverse the object structure. Prepare object

structure to let operations traverse it.

(40)

Sample Structure

40 Client

accept(Visitor) List

accept(Visitor) NIL

tail

Element

accept(Visitor) head: int

IntElement

accept(Visitor) head: char

CharElement visit(NIL)

visit(IntElement) visit(CharElement)

Visitor

visit(NIL)

visit(IntElement) visit(CharElement)

SumVisitor

visit(NIL)

visit(IntElement) visit(CharElement)

CharVisitor

(41)

Sample Implementation

public interface List {

void accept(Visitor visitor);

}

@Immutable // even stateless

public class Nil implements List {

@Override public void accept(Visitor visitor) { visitor.visit(this);

} }

public abstract class Element implements List { protected List tail;

protected Element(List tail) { this.tail = tail;

}

public List getTail() { return tail; } }

(42)

Sample Implementation

42

@Immutable

public class IntElement extends Element { private int head;

public IntElement(int number, List tail) { super(tail);

head = number;

}

public int getHead() { return head; }

@Override public void accept(Visitor visitor) { visitor.visit(this);

} }

public interface Visitor { void visit(Nil list);

void visit(IntElement list);

void visit(CharElement list);

}

(43)

Sample Implementation

@NotThreadSafe // must be run thread-confined public class SumVisitor implements Visitor { private int sum = 0;

@Override public void visit(Nil list) {}

@Override public void visit(IntElement list) { sum += list.getHead();

list.getTail().accept(this);

}

@Override public void visit(CharElement list) { list.getTail().accept(this);

}

public int getSum() { return sum; } }

Please note: visitor can (and sometimes must) store state

information.

(44)

Sample Implementation

44 public class Client {

public static void main(String[] args) { List l =

new IntElement(4,

new CharElement('b', new CharElement('a', new IntElement(3, new Nil()))));

SumVisitor sv = new SumVisitor();

l.accept(sv);

System.out.println("Summe: " + sv.getSum());

CharsVisitor cv = new CharsVisitor();

l.accept(cv);

System.out.println("Summe: " + cv.getChars());

} }

Sample Usage:

(45)

General Structure

Client

accept(Visitor) Element

accept(Visitor)

ConcreteElementA

accept(Visitor)

ConcreteElementB visit(ConcreteElementA)

visit(ConcreteElementB) Visitor

visit(ConcreteElementA) visit(ConcreteElementB)

ConcreteVisitor

Please note: Concrete elements need not have a common supertype.

• provides implementation for each overloaded visit operation

• can store state information

• provides operation to retrieve final state declares an overloaded visit operation

for each concrete element type.

defines accept operation with a visitor as argument.

implements accept operation, usually by

calling visitor’s appropriate

visit operation.

(46)

Consequences of Modifications

46

modify operation modify object structure w/o visitor

pattern adapt all

structural classes adapt only affected structural class

with visitor

pattern adapt only

affected visitor adapt all visitor classes

Visitor pattern is beneficial if object structure is more stable

than operations on it.

(47)

Advanced Considerations

might seem awkward. It is only necessary, because most OO languages (including Java and C#) support only single dispatch rather than double dispatch.

v: ConcreteVisitor e:ConcreteElement

e.accept(v)

visit(this) :Client

The interaction sequence

(48)

Advanced Considerations

48

Desirable:

‣ in client, call visitor’s appropriate overloaded visit operation, depending on element type

v: ConcreteVisitor e:ConcreteElement v.visit(e)

:Client

doube dispatch: selection of visit depends on both e ’s type

and v ’s runtime type

(49)

Advanced Considerations

single dispatch polymorhism: method call v.visit(e) depends on v ’s runtime type, but only on e ’s static type.

v: ConcreteVisitor e:ConcreteElement

e.accept(v)

visit(this) :Client

use single dispatch polymorhism to call correct e ’s accept method

use overloading to call

correct visit method.

(50)

Advanced Considerations

50

If client inspects the runtime type of an element, it can directly call the visitor’s appropriate visit operations without the

indirection via the element's accept operation:

...

if ( l instanceof Nil ) { v.visit((Nil) list);

}

if ( l instanceof IntElement ) { v.visit((IntElement) list);

}

if ( l instanceof CharElement ) { v.visit((CharElement) list);

}

(51)

Advanced Considerations

This task can be placed into visitor:

public abstract class Visitor {

public final void visit(List list) { if ( list instanceof Nil ) {

visit((Nil) list);

}

if ( list instanceof IntElement ) { visit((IntElement) list);

}

if ( list instanceof CharElement ) { visit((CharElement) list);

} }

abstract public void visit(Nil list);

abstract public void visit(IntElement list);

abstract public void visit(CharElement list);

}

(52)

Advanced Considerations

52

@NotThreadSafe // must be run thread-confined public class SumVisitor extends Visitor {

private int sum = 0;

public void visit(Nil list) {}

public void visit(IntElement list) { sum += list.getHead();

visit(list.getTail());

}

public void visit(CharElement list) { visit(list.getTail());

}

public int getSum() { return sum; } }

(53)

Advanced Considerations

public interface List {}

public class Nil implements List {}

public abstract class Element implements List { protected List tail;

protected Element(List tail) { this.tail = tail;

}

public List getTail() { return tail; } }

Now, list structure does not need accept method any more:

(54)

Advanced Considerations

54

@Immutable

public class IntElement extends Element { private int head;

public IntElement(int number, List tail) { super(tail);

head = number;

}

public int getHead() { return head; } }

public class Client {

public static void main(String[] args) {

List l = new IntElement(4, new CharElement('b',

new CharElement('a', new IntElement(3, new Nil()))));

SumVisitor sv = new SumVisitor();

sv.visit(l);

System.out.println("Summe: " + sv.getSum());

CharsVisitor cv = new CharsVisitor();

cv.visit(l);

System.out.println("Summe: " + cv.getChars());

} }

(55)

Even More Advanced Considerations

public abstract class Visitor {

final public void visit(List list) { Object[] os = {list};

Class<?>[] cs = {list.getClass()};

try {

this.getClass().getMethod("visit", cs).invoke(this, os);

}

catch ( Exception e) { ... } }

abstract public void visit(Nil list);

abstract public void visit(IntElement list);

abstract public void visit(CharElement list);

}

With introspection, the Visitor base class can do without

the switch:

Referenzen

ÄHNLICHE DOKUMENTE

German and European efforts to reduce irregular migration, particularly from sub- Saharan Africa, place a great emphasis on development co-operation.. The aim is for this to

In this vein, we align with several existing studies (Duvanova et al., 2015; Gruzd and Tsyganova, 2015; Karamshuk et al., 2016) and sug- gest using the case of Ukraine to

The review will be based on the results of ESPON projects ESPON 1.1.1 (Urban Areas as Nodes in Polycentric Develop- ment) and ESPON 1.1.3 (Enlargement of the European Union

Indepen- dent of the size or scope of its aid program, the US government should explicitly commit to maintaining policy engagement at the federal and provincial levels on

The reason for creating a specific SED for people working in more than one member state is that in these cases the determination can be provisional and the receiving Institution(s)

I Receiver must allow command object to restore receiver’s original state. I Client stores all executed commands in a

8 Moreover, developing countries insisted on excluding the so-called “Singapore issues” from the negotiations. These issues, identified during the WTO First

Even if the departure from gainful employment will be delayed in the future 88 and part of the time after working life will remain devoted to rest, leisure, and