Upload
others
View
7
Download
0
Embed Size (px)
Citation preview
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: