CS 635 Advanced Object-Oriented Design & Programming Spring Semester, 2001 Memento, Command, Command Processor |
||
---|---|---|
© 2001, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 21-Mar-01 |
Memento
Store an object's internal state, so the object can be restored to this state later without violating encapsulation
Motivation
Allow undo, rollbacks, etc.
Structure
Only originator:
Applicability
Use when you:
An Example
package Examples; class Memento { private Hashtable savedState = new Hashtable(); protected Memento() {}; //Give some protection protected void setState( String stateName, Object stateValue ) { savedState.put( stateName, stateValue ); } protected Object getState( String stateName) { return savedState.get( stateName); } protected Object getState(String stateName, Object defaultValue ) { if ( savedState.containsKey( stateName ) ) return savedState.get( stateName); else return defaultValue; } }
A Class whose state is Saved
package Examples; class ComplexObject { private String name; private int someData; private Vector objectAsState = new Vector(); public Memento createMemento() { Memento currentState = new Memento(); currentState.setState( "name", name ); currentState.setState( "someData", new Integer(someData) ); currentState.setState( "objectAsState", objectAsState.clone() ); return currentState; } public void restoreState( Memento oldState) { name = (String) oldState.getState( "name", name ); objectAsState = (Vector) oldState.getState( "objectAsState" ); Integer data = (Integer) oldState.getState( "someData"); someData = data.intValue(); }
// Show a way to do incremental saves public Memento setName( String aName ) { Memento deltaState = saveAState( "name", name); name = aName; return deltaState; }
public void setSomeData( int value ) { someData = value; } private Memento saveAState(String stateName, Object stateValue) { Memento currentState = new Memento(); currentState.setState( stateName, stateValue ); return currentState; } }
Consequences/ Implementation
Simplifies Originator
You may be tempted to let the originator manage its state history
This adds to the complexity of the Originator
Defining Narrow and Wide Interfaces
C++
Class Memento { public: virtual ~Memento(); private: friend class Originator; Memento(); void setState(State*); State* GetState(); ...
Java[1]
class ComplexObject { private String name; private int someData; public Memento createMemento() { return new Memento(); } public void restoreState( Memento oldState) { oldState.restoreStateTo( this ); } public class Memento { private String savedName; private int savedSomeData; private Memento() { savedName = name; savedSomeData = someData; } private void restoreStateTo(ComplexObject target) { target.name = savedName; target.someData = savedSomeData; } } }
Using Clone to Save State
One can wrap a clone of the Originator in a Memento or
Just return the clone as a type with no methods
interface Memento extends Cloneable { }
class ComplexObject implements Memento { private String name; private int someData; public Memento createMemento() { Memento myState = null; try { myState = (Memento) this.clone(); } catch (CloneNotSupportedException notReachable) { } return myState; } public void restoreState( Memento savedState) { ComplexObject myNewState = (ComplexObject)savedState; name = myNewState.name; someData = myNewState.someData; } }
Iterators & Mementos
Using a Momento we can allow multiple concurrent iterations
class IteratorState { int currentPosition = 0; protected IteratorState() {} protected int getPosition() { return currentPosition; } protected void advancePosition() { currentPosition++; } } class Vector { protected Object elementData[]; protected int elementCount; public IteratorState newIteration() { return new IteratorState(); } public boolean hasMoreElements(IteratorState aState) { return aState.getPosition() < elementCount; } public Object nextElement( IteratorState aState ) { if (hasMoreElements( aState ) ) { int currentPosition = aState.getPosition(); aState.advancePosition(); return elementData[currentPosition]; } throw new NoSuchElementException("VectorIterator"); } ...
Command
Encapsulates a request as an object
Structure
Example
Let
When to Use the Command Pattern
Consequences
Command decouples the object that invokes the operation from the one that knows how to perform it
It is easy to add new commands, because you do not have to change existing classes
You can assemble commands into a composite object
Example - Menu Callbacks
abstract class Command { abstract public void execute(); } class OpenCommand extends Command { private Application opener; public OpenCommand( Application theOpener ) { opener = theOpener; } public void execute() { String documentName = AskUserSomeHow(); if ( name != null ) { Document toOpen = new Document( documentName ); opener.add( toOpen ); opener.open(); } } }
Using Command class Menu { private Hashtable menuActions = new Hashtable(); public void addMenuItem( String displayString, Command itemAction ) { menuActions.put( displayString, itemAction ); } public void handleEvent( String itemSelected ) { Command runMe; runMe = (Command) menuActions.get( itemSelected ); runMe.execute(); } // lots of stuff missing }
MacroCommand
class MacroCommand extends Command { private Vector commands = new Vector(); public void add( Command toAdd ) { commands.addElement( toAdd ); } public void remove( Command toRemove ) { commands.removeElement( toAdd ); } public void execute() { Enumeration commandList = commands.elements(); while ( commandList.hasMoreElements() ) { Command nextCommand; nextCommand = (Command) commandList.nextElement(); nextCommand.execute(); } } }
Pluggable Commands
Using reflection it is possible to create one general Command
import java.util.*; import java.lang.reflect.*;
public class Command { private Object receiver; private Method command; private Object[] arguments; public Command(Object receiver, Method command, Object[] arguments ) { this.receiver = receiver; this.command = command; this.arguments = arguments; } public void execute() throws InvocationTargetException, IllegalAccessException { command.invoke( receiver, arguments ); } }
Using the Pluggable Command
One does have to be careful with the primitive types
public class Test { public static void main(String[] args) throws Exception { Vector sample = new Vector(); Class[] argumentTypes = { Object.class }; Method add = Vector.class.getMethod( "addElement", argumentTypes); Object[] arguments = { "cat" }; Command test = new Command(sample, add, arguments ); test.execute(); System.out.println( sample.elementAt( 0)); } }Output cat
Pluggable Command Smalltalk Version
Object subclass: #PluggableCommand instanceVariableNames: 'receiver selector arguments ' classVariableNames: '' poolDictionaries: '' category: 'Whitney-Examples'
Class Methods receiver: anObject selector: aSymbol arguments: anArrayOrNil ^super new setReceiver: anObject selector: aSymbol arguments: anArrayOrNil
Instance Methods setReceiver: anObject selector: aSymbol arguments: anArrayOrNil receiver := anObject. selector := aSymbol. arguments := anArrayOrNil isNil ifTrue:[#( )] ifFalse: [anArrayOrNil]
execute ^receiver perform: selector withArguments: arguments
Using the Pluggable Command
| sample command | sample := OrderedCollection new. command := PluggableCommand receiver: sample selector: #add: arguments: #( 5 ). command execute. ^sample at: 1
Command Processor
Similar to the command pattern
Command Processor manages the command objects
The command processor:
Structure
Dynamics
Consequences
Benefits
Liabilities
[1] RestoreStateTo does not access the fields of the outer object in case one wants to restore the state to a different ComplexObject object. One may wish to use an nested class to avoid tangling the memento to the outer object
Copyright ©, All rights reserved.
2001 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.
Previous    visitors since 21-Mar-01    Next