CS 635 Advanced Object-Oriented Design & Programming Spring Semester, 2002 State, Proxy, Adaptor |
||
---|---|---|
© 2002, All Rights Reserved, SDSU & Roger Whitney San Diego State University -- This page last updated 28-Feb-02 |
Design Principle 2
Favor object composition over class inheritance
Composition
class A { Foo x public int complexOperation() { blah } } class B extends A { public void bar() { blah} }
class B { A myA; public int complexOperation() { return myA.complexOperation() } public void bar() { blah} }
State
Example - SPOP
Simple Post Office Protocol
SPOP is used to download e-mail from a server
SPOP supports the following command:
RETR Command
Arguments: a message-number
Returns: the mail message indicated by the number
QUIT Command
Arguments: none
Updates mail box to reflect transactions taken during the transaction state, then logs user out
If session ends by any method except the QUIT command, the updates are not done
The Switch Statement
class SPop { static final int HAVE_USER_NAME = 2; static final int START = 3; static final int AUTHORIZED = 4; private int state = START; String userName; String password; public void user( String userName ) { switch (state) { case START: { this.userName = userName; state = HAVE_USER_NAME; break; } case HAVE_USER_NAME: case AUTHORIZED:{ endLastSessionWithoutUpdate(); goToStartState() } } }
Implementation with Switch Statement Cont.
public void pass( String password ) { switch (state) { case START: { giveWarningOfIllegalCommand(); } case HAVE_USER_NAME: { this.password = password; if ( validateUser() ) state = AUTHORIZED; else { sendErrorMessageOrWhatEver(); userName = null; password = null; state = START; } case AUTHORIZED: { endLastSessionWithoutUpdate(); goToStartState() } } } etc. }
Using Polymorphism Implementation
class SPop { private SPopState state = new Start(); public void user( String userName ) { state = state.user( userName ); } public void pass( String password ) { state = state.pass( password ); } public void list( int messageNumber ) { state = state.list( massageNumber ); } etc.
SPopStates Defines default behavior
abstract class SPopState { public SPopState user( String userName ) { return goToStartState(); } public SPopState pass( String password ) { return goToStartState(); } public SPopState list( int massageNumber ) { return goToStartState(); } public SPopState retr( int massageNumber ) { return goToStartState(); } public SPopState quit( ) { return goToStartState(); } protected SPopState goToStartState() { endLastSessionWithoutUpdate(); return new StartState(); } }
SpopStates - Continued
class Start extends SPopState { public SPopState user( String userName ) { return new HaveUserName( userName ); } } class HaveUserName extends SPopState { String userName; public HaveUserName( String userName ) { this.userName = userName; } public SPopState pass( String password ) { if ( validateUser( userName, password ) return new Authorized( userName ); else return goToStartState(); } }
State Intent
Allow an object to alter its behavior when its internal state changes. The object will appear to change it class.
Applicability
Use the State pattern in either of the following cases:
Issues
How much State in the State
In Example:
Issue Who defines the state transitions?
The Context
class SPop { private SPopState state = new Start(); public void user( String userName ) { state.user( userName ); state = new HaveUserName( userName ); } public void pass( String password ) { if ( state.pass( password ) ) state = new Authorized( ); else state = new Start(); }
Who defines the state transitions?
The State
class SPop { private SPopState state = new Start(); public void user( String userName ) { state = state.user( userName ); } public void pass( String password ) { state = state.pass( password ); } public void list( int messageNumber ) { state = state.list( massageNumber ); }
Issue Sharing State Objects
Multiple contexts (SPops) can use the same state object if the state object has no instance variables
A state object can have no instance variables if:
Storing Instance Variables Elsewhere
Variant 1
SPop stores them and passes them to states
class SPop { private SPopState state = new Start(); String userName; String password; public void user( String newName ) { this.userName = newName; state.user( newName ); } public void pass( String password ) { state.pass( userName , password ); }
Storing Instance Variables Elsewhere
Variant 2
SPop stores them and states get data from SPop
class SPop { private SPopState state = new Start(); String userName; String password; public String userName() { return userName; } public String password() { return password; } public void user( String newName ) { this.userName = newName ; state.user( this ); } etc.
class HaveUserName extends SPopState { public SPopState pass( SPop mailServer ) { String useName = mailServer.userName(); etc. } }
Issue Creating and Destroying State Objects
Options:
Issue Changing the Context Class for Real
Some languages allow an object to change its class
| context | context := Start new. context changeClassTo: HaveUserName. context changeClassTo: Authorized.So why not forget State pattern and use:
Consequences
State Verses Strategy
How to tell the difference
Rate of Change Strategy
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:
Adapter
Motivating Adapter
Java CGI & Servlets
Both Java CGI and servlets are used for server-side processing of certain HTML requests, like processing HTML forms
Servlets have greater functionality and are faster, but require special Web servers or servers with special extensions
To help write Java CGI programs there is class sdsu.net.CGI
It would be useful in moving code between servers to avoid having to rewrite the code
One Problem
One issue is access to the CGI environment variables
There are about 20 common CGI environment variables
In servlets one has an HttpRequest class that has a getX() method for each CGI environment variable
sdsu.net.CGI class returns a hash table with one entry per CGI environment variable
Solution
We can write a wrapper around HttpRequest to make it act like a hash table
The Wrapper or Adapter
class CGIAdapter extends Hashtable { Hashtable CGIvariables = new Hashtable( 20); public CGIAdapter( HttpRequest CGIEnvironment ) { CGIvariables.put( "AUTH_TYPE" , CGIEnvironment.getAuthType()); CGIvariables.put( "REMOTE_USER" , CGIEnvironment.getRemoteUser()); etc. } public Object get(Object key) { return CGIvariables.get( key ); } etc. }
Going the other Direction
Adapting servlet code to normal CGI requires extracting the CGI environment variables out of the hash table and putting them into an object that implements the public interface of the HttpRequest class
class HTTPAdapter extends HttpRequest { Hashtable CGIvariables; public HTTPAdapter( Hashtable CGIEnvironment ) { CGIvariables = CGIEnvironment; } public String getAuthType() { return (String) CGIvariables.get( "AUTH_TYPE" ); } public String getRemoteUser() { return (String) CGIvariables.get( "REMOTE_USER" ); } etc. }
Adapter
The adapter pattern converts the interface of a class into another interface.
Use the Adapter pattern when
Class Adapter
Object Adapter
Class Adapter Example
class OldSquarePeg { public: void squarePegOperation() { do something } } class RoundPeg { public: void virtual roundPegOperation = 0; } class PegAdapter: private OldSquarePeg, public RoundPeg { public: void virtual roundPegOperation() { add some corners; squarePegOperation(); } } void clientMethod() { RoundPeg* aPeg = new PegAdapter(); aPeg->roundPegOperation(); }
Object Adapter Example
class OldSquarePeg { public: void squarePegOperation() { do something } } class RoundPeg { public: void virtual roundPegOperation = 0; } class PegAdapter: public RoundPeg { private: OldSquarePeg* square; public: PegAdapter() { square = new OldSquarePeg; } void virtual roundPegOperation() { add some corners; square->squarePegOperation(); } }
Consequences
A Class adapter uses inheritance so
How Much Adapting does the Adapter do?
The adapter may have to work very little or a great deal to adapt the Adaptee to the Target
The Adapter may just map one operation to another
class PegAdapter: public RoundPeg { private: OldSquarePeg* square; public: PegAdapter() { square = new OldSquarePeg;} void roundPegOperation() { square->squarePegOperation(); } }
The Adapter may have to work hard if the Target operation does not have a comparable operation in the Adaptee
Pluggable Adapters
In the CGI example we adapted a class with getX() methods to a hash table interface
It is likely that we may adapt a class with getX() methods to a hashtable in the future
It would be nice to write one class to do all such adapting
This class would be given a list of keys to getX methods and an Adaptee object
HttpRequest CGIEnvironment = getHttpRequest(); PluggableHashAdapter sample = new PluggableHashAdapter( CGIEnvironment ); sample.adapt( "AUTH_TYPE" , getAuthType ); sample.adapt( "REMOTE_USER" , getRemoteUser ); etc. sample.get( “REMOTE_USER” );
Pluggable Adapters are used in interface components, where we know in advance that we will adapt the component to other interfaces
Pluggable Adapters are common in Smalltalk, were it is easier to map strings to method calls
Using two-way Adapter
In the SquarePeg-RoundPeg example the SquarePeg is adapted to the RoundPeg
So a SquarePeg can be used where a RoundPeg is needed, but not the other way around.
A two-way adapter would also allow a RoundPeg be used in place of the SquarePeg
class OldSquarePeg { public: void virtual squarePegOperation() { blah } } class RoundPeg { public: void virtual roundPegOperation() { blah } } class PegAdapter: public OldSquarePeg, RoundPeg { public: void virtual roundPegOperation() { add some corners; squarePegOperation(); } void virtual squarePegOperation() { add some corners; roundPegOperation(); } }
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 28-Feb-02    Next