design patterns - hochschule konstanzhaase/lehre/patterns/slides/c11_behavioral1.pdfiterating...

Post on 24-Jun-2020

7 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Oliver Haase

Design PatternsCommand

1

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

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

3

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

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.

5

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

Sample Structure

7

MyApplication

actionPerformed()

ActionListener

action()

MyModel

model.action()

actionPerformed()

model

MyActionListener

actionListener

JButton

ApplicabilityUse 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

General Structure

9

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.

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

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

11

How to implement chains of undos and redos:

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:

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

13

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

Iterator

15

Purpose

16

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

Also known as: Cursor

Sample Structure

17

Client

iterator()

List

iterator()

ArrayList

hasNext()

next()

Iterator

hasNext()

next()

ArrayListIterator

iterator()

Vector

hasNext()

next()

VectorIterator

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

General Structure

19

Client

createIterator()

Aggregate

return new ConcreteIterator()

createIterator()ConcreteAggregate

hasNext()

next()

Iterator

hasNext()next()

ConcreteIterator

•implements Iterator interface•stores current position of traversal

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.

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

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

traversed

‣ robust iterators don't get affected→ possible techniques:

21

• iterator works on deep copy of aggregate → potentially out-dated snapshot

• iterator is registered with aggregate, aggregate notifies iterators about modifications

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.

Java Iterator Interface

23

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

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

Java Iterator InterfacePossible Solutions:

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

2. client-side locking:

25

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!

Java Iterator InterfaceWhat’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!

Java Iterator InterfaceSolution: modify vector through iterator, not directly.

27

public static void filterVector(Vector<T> vector) { for (Iterator<T> it = vector.iterator(); it.hasNext(); ) if ( ! it.next().isValid() ) it.remove();}

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!

Hidden Iterators

29

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

Iterating Concurrent Collections Since 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!

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.

31

Visitor

32

Purpose

33

Separate algorithm from the object structure upon which it operates.

MotivationConsider the following list structure:

34

Client

sum(): intchars(): String

List

sum(): intchars(): String

NIL

tailElement

sum(): int chars: String

head: intIntElement

sum(): int chars: String

head: charCharElement

[compare: J. Bloch, Effective Java, 2nd Edition, item 43: Return empty arrays or collections, not null]

Motivation

35

public interface List { int sum(); String chars();}

@Immutable // even statelesspublic 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; }}

Motivation

36

@Immutablepublic 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(); }}

@Immutablepublic 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(); }}

Motivation

37

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:

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

Key Idea

39

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

Sample Structure

40

Client

accept(Visitor)List

accept(Visitor)NIL

tailElement

accept(Visitor)head: intIntElement

accept(Visitor)head: charCharElement

visit(NIL)visit(IntElement)visit(CharElement)

Visitor

visit(NIL)visit(IntElement)visit(CharElement)

SumVisitor

visit(NIL)visit(IntElement)visit(CharElement)

CharVisitor

Sample Implementation

41

public interface List { void accept(Visitor visitor);}

@Immutable // even statelesspublic 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; }}

Sample Implementation

42

@Immutablepublic 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);}

Sample Implementation

43

@NotThreadSafe // must be run thread-confinedpublic 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.

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:

General Structure

45

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.

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.

Advanced Considerations

47

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

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

Advanced Considerations

49

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

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

Advanced Considerations

51

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

Advanced Considerations

52

@NotThreadSafe // must be run thread-confinedpublic 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; }}

Advanced Considerations

53

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:

Advanced Considerations

54

@Immutablepublic 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()); }}

Even More Advanced Considerations

55

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:

top related