SDSU CS 635 Advanced Object-Oriented Design & Programming
Spring Semester, 2002
Command & Memento
Previous    Lecture Notes Index    Next    
© 2002, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 08-Apr-02


References

Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides, Addison-Wesley, 1995, pp. 233-242, Command Pattern, pp. 283-291

The Design Patterns Smalltalk Companion, Alpert, Brown, Woolf, Addision-Wesley, 1998, pp. 297-304, 245-260

Pattern-Oriented Software Architecture: A System of Patterns, Buschman, Meunier, Rohnert, Sommerlad, Stal, 1996, pp. 277-290, Command Processor

Command Processor, Sommerlad in Pattern Languages of Program Design 2, Eds. Vlissides, Coplien, Kerth, Addison-Wesley, 1996, pp. 63-74

Advanced C++: Programming Styles and Idioms, James Coplien, Addison Wesley, 1992, pp 165-170, Functor Pattern

Doc 12, Command & Memento Slide # 2

Command


Encapsulates a request as an object

Structure




Example

Let
Invoker be a menu
Client be a word processing program
Receiver a document
Action be save


Doc 12, Command & Memento Slide # 3

When to Use the Command Pattern


Commands replace callback functions





A Transactions encapsulates a set of changes to data

Systems that use transaction often can use the command pattern



Doc 12, Command & Memento Slide # 4

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


Doc 12, Command & Memento Slide # 5
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();
         }
      }
   }


Doc 12, Command & Memento Slide # 6
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
   }



Doc 12, Command & Memento Slide # 7
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();
         }
      }
   }


Doc 12, Command & Memento Slide # 8
Pluggable Commands

Using reflection it is possible to create one general Command

Don’t hard code the method called in the command

Pass the method to call an argument


Doc 12, Command & Memento Slide # 9
Java Example of Pluggable 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 );
      }
   }

Doc 12, Command & Memento Slide # 10
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


Doc 12, Command & Memento Slide # 11
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




Doc 12, Command & Memento Slide # 12
Using the Pluggable Command

| sample command |
sample := OrderedCollection new.
command := PluggableCommand
      receiver: sample
      selector: #add:
      arguments: #( 5 ).
command execute.
^sample at: 1


Doc 12, Command & Memento Slide # 13

Command Processor


Command Processor manages the command objects


The command processor:








Doc 12, Command & Memento Slide # 14

Structure



Dynamics




Doc 12, Command & Memento Slide # 15

Consequences

Benefits


Different user interface elements can generate the same kind of command object

Allows the user to configure commands performed by a user interface element


Adding new commands and providing for a macro language comes easy


Commands can be stored for later replay
Commands can be logged
Commands can be rolled back



Allows for the execution of commands in separate threads



Doc 12, Command & Memento Slide # 16
Liabilities



Try reducing the number of command classes by:
Grouping commands around abstractions
Unifying simple commands classes by passing the receiver object as a parameter


How do commands get additional parameters they need?


Doc 12, Command & Memento Slide # 17

Functor

Functions as Objects

A functor is a class with



Functors are functions that behave like objects

They serve the role of a function, but can be created, passed as parameters, and manipulated like objects

final class StudentNameComparator implements Comparator {
   
   public int compare( Object leftOp, Object rightOp ) {
      String leftName = ((Student) leftOp).name;
      String rightName = ((Student) rightOp).name;
      return leftName.compareTo( rightName );
   }
}



Doc 12, Command & Memento Slide # 18
How Does a Functor Compare to Function Pointers?


How does A Functor compare with Command?



When to use the Functor

Coplien states:

Use functors when you would be tempted to use function pointers

Functors are commonly used for callbacks

Doc 12, Command & Memento Slide # 19

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:




Doc 12, Command & Memento Slide # 20

Applicability


Use when you:




Doc 12, Command & Memento Slide # 21
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;
      } 
   }



Doc 12, Command & Memento Slide # 22
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();
      }




Doc 12, Command & Memento Slide # 23
   // 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;
      }   
   }



Doc 12, Command & Memento Slide # 24

Consequences/ Implementation


Simplifies Originator

You may be tempted to let the originator manage its state history

This adds to the complexity of the Originator

How to store state history and for how long?

Using Mementos might be expensive

Copying state takes time and space

If this takes too much time/space pattern may not be appropriate

Preserve encapsulation boundaries

Give Memento two interfaces: wide and narrow

Let originator have access to all set/get/state of Memento

Let others only hold Mementos and destroy them


Doc 12, Command & Memento Slide # 25
Defining Narrow and Wide Interfaces

C++

Make Memento's interface private
Make Originator a friend of the Memento
   Class Memento {
   public:
      virtual ~Memento();
   private:
      friend class Originator;
      Memento();
      void setState(State*);
      State* GetState();
      ...


Doc 12, Command & Memento Slide # 26
Java[1]

Use private nested/inner class to hide memento's interface

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


Doc 12, Command & Memento Slide # 27
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;
   }
}


Doc 12, Command & Memento Slide # 28

Iterators & Mementos


Using a Memento 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");
   }
   ...


Doc 12, Command & Memento Slide # 29
[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.
2002 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 08-Apr-02    Next