CS 635 Advanced Object-Oriented Design & Programming Spring Semester, 2004 Command, Decorator & Proxy |
||
---|---|---|
© 2004, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 19-Feb-04 |
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(); } } }
Prevayler
http://www.prevayler.org/wiki.jsp
Prevalence layer for Java
Database that
Restaurant Example
import java.util.*; import org.prevayler.implementation.AbstractPrevalentSystem; public class Restaurant extends AbstractPrevalentSystem { private String name; ArrayList ratings = new ArrayList(); public Restaurant(String newName) { name = newName;} public String name() {return name;} public void addRating( int newRating) { ratings.add( new Integer(newRating)); } public float getRating() { if (ratings.size() == 0 ) return -1; int total = 0; for (int k =0; k < ratings.size();k++) total = total + ((Integer)ratings.get(k)).intValue(); return total/ ratings.size(); } }
Command
import java.io.Serializable; import org.prevayler.Command; import org.prevayler.PrevalentSystem; public class AddRatingCommand implements Command { private final int newRating; public AddRatingCommand(int rating) { newRating = rating; } public Serializable execute(PrevalentSystem system) { ((Restaurant)system).addRating(newRating); return null; } }
First Run
import java.util.*; import org.prevayler.implementation.SnapshotPrevayler; public class PrevaylerExample { public static void main (String args[]) throws Exception { SnapshotPrevayler samsDinerData = new SnapshotPrevayler(new Restaurant("Sams Diner"), "food"); System.out.println( “Start”); Restaurant samsDiner = (Restaurant) samsDinerData.system(); System.out.println( samsDiner.getRating() ); samsDinerData.executeCommand( new AddRatingCommand( 5)); System.out.println( samsDiner.getRating() ); } }
Output Recovering system state...
Start
-1.0
5.0
Second Run
public class PrevaylerExample { public static void main (String args[]) throws Exception { SnapshotPrevayler samsDinerData = new SnapshotPrevayler(new Restaurant("Sams Diner"), "food"); System.out.println( “Start”); Restaurant samsDiner = (Restaurant) samsDinerData.system(); System.out.println( samsDiner.getRating() ); samsDinerData.executeCommand( new AddRatingCommand( 10)); System.out.println( samsDiner.getRating() ); } }
Output Recovering system state...
Reading food/000000000000000000001.commandLog...
Start
5.0
7.0
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
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 ); } }
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
Command Processor manages the command objects
The command processor:
Structure
Dynamics
Consequences
Benefits
Liabilities
Functor
Functions as Objects
A functor is a class with
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 ); } }
How Does a Functor Compare to Function Pointers?
Decorator
Changing the Skin of an Object
Class Structure
Runtime Structure
Motivation - Text Views
A text view has the following features:
Solution 1 - Use Object Composition
class TextView { Border myBorder; ScrollBar verticalBar; ScrollBar horizontalBar; public void draw() { myBorder.draw(); verticalBar.draw(); horizontalBar.draw(); code to draw self } etc. }
But TextView knows about all the variations!
New type of variations require changing TextView
(and any other type of view we have)
Solution 2 - Use Decorator
Object Composition Inside out
Change the skin of an object not it guts
TextView has no borders or scrollbars!
Add borders and scrollbars on top of a TextView
Runtime Structure
Applicability
Use Decorator:
if ( aComponent instanceof TextView ) blah
Implementation Issues
Keep Decorators lightweight
Don't put data members in VisualComponent
Have Decorator forward all component operations
Three ways to forward messages
Examples
Java Streams
import java.io.*; import sdsu.io.*; class ReadingFileExample { public static void main( String args[] ) throws Exception { FileInputStream inputFile; BufferedInputStream bufferedFile; ASCIIInputStream cin; inputFile = new FileInputStream( "ReadingFileExample.java" ); bufferedFile = new BufferedInputStream( inputFile ); cin = new ASCIIInputStream( bufferedFile ); System.out.println( cin.readWord() ); for ( int k = 1 ; k < 4; k++ ) System.out.println( cin.readLine() ); } }
Insurance
Insurance policies have payment caps for claims
Sometimes the people with the same policy will have different caps
A decorator can be used to provide different caps on the same policy object
Similarly for deductibles & copayments
Proxy
proxy n. pl prox-ies The agency for a person who acts as a substitute for another person, authority to act for another
Structure
The Pattern
The proxy has the same interface as the original object
Use common interface (or abstract class) for both the proxy and original object
Proxy contains a reference to original object, so proxy can forward requests to the original object
Dynamics
Runtime Objects
Sample Proxy
public class Proxy { Foo target; public float bar(int size ) { preprocess here float answer = target.bar( size); postProcess here return answer; } other methods as needed }
Preprocessing & post-processing depend on purpose of the proxy
Reasons for Object Proxies
Remote Proxy The actual object is on a remote machine (remote address space)
Hide real details of accessing the object
Used in CORBA, Java RMI
public class HelloClient { public static void main(String args[]) { try { String server = getHelloHostAddress( args); Hello proxy = (Hello) Naming.lookup( server ); String message = proxy.sayHello(); System.out.println( message ); } catch ( Exception error) { error.printStackTrace(); } } }
Reasons for Object Proxies Continued
Virtual Proxy
Synchronization Proxy
public class Table { public Object elementAt( int row, int column ){ blah } public void setElementAt(Object element, int row, int column ) { blah} } public class RowLockTable { Table realTable; Integer[] locks; public RowLockTable( Table toLock) { realTable = toLock; locks = new String[ toLock.numberOfRows() ]; for (int row = 0; row< toLock.numberOfRows(); row++ ) locks[row] = new Integer(row); } public Object elementAt( int row, int column ) { synchronized ( locks[row] ) { return realTable.elementAt( row, column); } } public void setElementAt(Object element, int row, int column ){ synchronized ( locks[row] ) { return realTable.setElementAt(element, row, column); } } }
Counting Proxy
Delete original object when there are no references to it
Prevent accidental deletion of real subject
Collect usage statistics
Sample use is making C++ pointer safe
Smalltalk Proxy Tricks
When an object is sent a message
The object's class and the object's class’s superclasses are searched for the method
If the method is not found the object is sent the message:
Prototyping of a Proxy
One can use doesNotUnderstand: to implement a pluggable proxy
Example
Object subclass: #Proxy instanceVariableNames: 'target ' classVariableNames: '' poolDictionaries: '' category: 'Whitney-Examples'
Class Method
on: anObject ^super new target: anObject
Instance Methods
doesNotUnderstand: aMessage ^target perform: aMessage selector withArguments: aMessage arguments
target: anObject target := anObject
Examples of Using the Proxy
| wrapper | wrapper := Proxy on: Transcript. wrapper open. wrapper show: 'Hi mom'.
| wrapper | wrapper := Proxy on: 3. wrapper + 5.
| wrapper | wrapper := Proxy on: 'Hi '. wrapper , ' mom'.
Why just for Prototyping
doesNotUnderstand: