SDSU CS 635: Advanced Object-Oriented Design & Programming
Spring Semester, 1998
Flyweight & Memento

To Lecture Notes Index
© 1998, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 05-May-98

Contents of Doc 29, Flyweight & Memento

CS 635 Doc 29 Flyweight & Memento
Flyweight slide # 2
...Motivation slide # 2
...Structure slide # 4
......Applicability slide # 5
...Implementation slide # 8
Memento slide # 9
...Motivation slide # 9
...Structure slide # 10
...Applicability slide # 10
...An Example slide # 11
...Consequences/ Implementation slide # 14
Iterators, Mementos slide # 15
...Momentos & Internal Iterators slide # 16
...Trees & Iteration slide # 17
...Composite Structures & Iteration slide # 20

References
Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides, Addison Wesley, 1995, pp 195-206, 283-293

Doc 29, Flyweight & Memento Slide # 2

Flyweight

Motivation


When a program needs a large number of objects, the space requirements can be come too large

The example in the text uses objects to represent individual characters of the alphabet

Using objects allows the program to treat characters like any other part of the document - as an object

A character, like "G", may require a fair amount of information:

The character type - G not h or y
Its font
Its width, height, ascenders, descenders, etc.
How to display/print the character
Where it is in the document

Most of this information is the same for all instances of the same letter


Doc 29, Flyweight & Memento Slide # 3
Intrinsic State

Information that is independent from the objects context

The information that can be shared among many objects

Extrinsic State

Information that is dependent on the objects context

The information that can not be shared among objects

So create one instance of the object and use the same object wherever you need an instance

The one object can store the intrinsic state, but someone else needs to store the extrinsic state for each context of the object


Doc 29, Flyweight & Memento Slide # 4

Structure

Flyweight Structure


Doc 29, Flyweight & Memento Slide # 5

Applicability


The pattern can be used when all the following are true


Doc 29, Flyweight & Memento Slide # 6
Example



State transitions

Start state comes first
When get user name transfer to HaveUserName state
When get correct password transfer to Authorized state

HaveUserName and Authorized states need the user name

In concurrent server we may have many (100's ) different users connected at the same time.

State creation can become expensive, so create each state once

Each time use the state pass the user name to the state


Doc 29, Flyweight & Memento Slide # 7
class HaveUserName extends SPopState
     {
     private HaveUserName() {};
     private static HaveUserName instance;
          
     public getInstance( )
          {
          if ( instance == null ) 
               instance = new HaveUserName();
          return instance;
          }
          
     public SPopState pass( String userName, String password )
          {
          if ( validateUser( userName, password )
               return Authorized.getInstance();
          else
               return Start.getInstance();
          }
     }


Doc 29, Flyweight & Memento Slide # 8

Implementation


Separating state from the flyweight

This is the hard part

Must remove the extrinsic state from the object

Store the extrinsic state elsewhere taking up less space

Each time you use the flyweight you must give it the proper extrinsic state

Managing Flyweights

Cannot use object identity on flyweights

Need factory to create flyweights, cannot create directly

How do we know when we are done with a flyweight?


Doc 29, Flyweight & Memento Slide # 9

Memento


Store an object's internal state, so the object can be restored to this state later

Motivation


Allow undos, rollbacks, etc.

But we have Command and Clone, why do we need another pattern for undo?

Clone can be more expensive than we need

Only part of the state of an object may change, cloning will copy all the state

Cloning can be hard to implement

Replacing an object with a clone does not work when other objects have references to the object to rollback

Command can apply an inverse function to undo an operation, but an inverse function may not exist or be hard to compute


Doc 29, Flyweight & Memento Slide # 10

Structure

Memento Structure

Only originator:

can access Memento's get/set state methods

create Memento

Applicability


Use when you:

need to save all or part of the state of an object and

do not wish to expose the saved state to the outside world

Doc 29, Flyweight & Memento Slide # 11

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 29, Flyweight & Memento Slide # 12
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 29, Flyweight & Memento Slide # 13

     // 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 29, Flyweight & Memento Slide # 14

Consequences/ Implementation


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

C++ has good support for Memento

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

Some languages make Memento easy to implement (C++) others do not (Java)

Doc 29, Flyweight & Memento Slide # 15

Iterators, Mementos, Trees, Polymorphism


We have seen that there are different types of iterators

Internal (passive)
External (active)

Internal

The object we are iterating over is also the iterator

The object contains the code to iterate and maintains a pointer to the current location

This means only one iterator on the structure at a time

class Vector  {
     protected Object elementData[];
     protected int elementCount;
     protected int currentPosition;
     
     public boolean hasMoreElements() {
          return currentPosition < elementCount;
     }

     public Object nextElement() {
          if (currentPosition < elementCount) 
               return elementData[currentPosition++];
     throw new NoSuchElementException("VectorIterator");
     }
     
     // all vector methods not shown
}

Doc 29, Flyweight & Memento Slide # 16

Momentos & Internal Iterators


Using a Momento we can allow multiple concurrent iterations with an internal iterator

class IteratorState {
     int currentPosition = 0;
     
     protected IteratorState() {}
     
     protected int getPosition() {     return currentPosition;     }
     
     protected void advancePosition() { currentPosition++; }
     // Other methods?
     }
     
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");
     }
     // all vector methods not shown
}


Doc 29, Flyweight & Memento Slide # 17

Trees & Iteration


Iteration with trees is more interesting as one needs to store more than the current position

In a given location there may be three directions to go

An iteration may go through the same node three times, may need to store the which visit this is to a particular node

If the tree does not support parent pointers one needs to store the stack of node from the current node to the root

Tree Traversal

public class TraversalStorage
     {
     BinaryNode storedNode;
     int visitNumber = 1;
     
     public TraversalStorage( BinaryNode aNode, int visitNumber )
          {
          storedNode = aNode;
          this.visitNumber = visitNumber;
          }
     }


Doc 29, Flyweight & Memento Slide # 18
A Traversal Algorithm
The following code does visit each node, but does nothing at
each node

import java.util.Stack;

public class TreeTraversal {
     Stack history = new Stack();
     
     BinaryNode currentNode;
     int visitNumber;
     
     public TreeTraversal( BinaryNode startNode ) {
          currentNode = startNode;
          visitNumber = 1;
     }
     
     public void VisitAllNodes() {
          while ( ( ! history.empty() ) && ( visitNumber != 3)) {
               switch ( visitNumber ) {
                    case 1: 
                         processFirstVisit( currentNode );
                         break;
                    case 2:
                         processSecondVisit( currentNode );
                         break;
                    case 3:
                         processThirdVisit( currentNode );
                         break;
               }
          }
     }


Doc 29, Flyweight & Memento Slide # 19

private void processFirstVisit( BinaryNode currentNode) {
          if ( currentNode.getLeftChild() != null )  {
               history.push( new TraversalStorage( currentNode, 2));
               currentNode = (BinaryNode )currentNode.getLeftChild();
               visitNumber = 1;
          }
          else
               processSecondVisit( currentNode );
     }
     
     private void processSecondVisit( BinaryNode currentNode) {
          if ( currentNode.getRightChild() != null )  {
               history.push( new TraversalStorage( currentNode, 3));
               currentNode = (BinaryNode) currentNode.getRightChild();
               visitNumber = 1;
          }
          else
               processThirdVisit( currentNode );
     }

     private void processThirdVisit( BinaryNode currentNode) {
          if ( ! history.empty() ){
               TraversalStorage nextNode = 
                    (TraversalStorage) history.pop();
               currentNode = nextNode.storedNode;
               visitNumber = nextNode.visitNumber;
          }
     }
}


Doc 29, Flyweight & Memento Slide # 20

Composite Structures & Iteration


The above code assumes that all nodes in the structure are the same type!

If the tree is contains BinaryNodes, UnaryNodes, and ExternalNodes, the above code will not work

We could add a selection for the type of the node to get code that looks something like:

private void processFirstVisit( Node currentNode) {
          if ( Node.type() == BINARY )
               processFirstBinaryVisit( (BinaryNode) currentNode);

          else if ( Node.type() == UNARY )
               processFirstUnaryVisit( (UnaryNode) currentNode);

          else if ( Node.type() == EXTERNAL )
               processFirstExternalVisit( (ExternalNode) currentNode);
     }

How to avoid checking the type of each node?


Doc 29, Flyweight & Memento Slide # 21
Use the Visitor Pattern!

The following code can be modified to perform inorder, preorder and post order

If we were just doing binary trees this code is overly complex

The design patterns are not "needed" for small programming assignments done at school


In BinaryNode:

     public void accept( Visitor aVisitor) {
          aVisitor.visitBinaryNode( this );
     }

In ExternalNode:

     public void accept( Visitor aVisitor){
          aVisitor.visitExternalNode( this );
     }

In UnaryNode:

     public void accept( Visitor aVisitor){
          aVisitor.visitUnaryNode( this );
     }


Doc 29, Flyweight & Memento Slide # 22

import java.util.Stack;

public class TraversalVisitor implements Visitor {
     Node currentNode;
     int visitNumber;
     int traversalType;
     
     Stack traversalPath = new Stack();
     
     public TraversalVisitor( Node startNode ) {
          currentNode = startNode;
          visitNumber = 1;
     }
     
     public boolean hasMoreElements() {
          if ( ( traversalPath.empty() ) && ( visitNumber >= 3
))
               return false;
          else
               return true;
     }
          
     public Node nextElement() {
          currentNode.accept( this );
          return currentNode;
     }



Doc 29, Flyweight & Memento Slide # 23

public void visitBinaryNode( BinaryNode aNode) {
          switch ( visitNumber ) {
               case 1:
                    pushNode( aNode, 2);
                    currentNode = aNode.getLeftChild();
                    visitNumber = 1;
                    break;
               case 2:
                    pushNode( aNode, 3);
                    currentNode = aNode.getRightChild();
                    visitNumber = 1;
                    break;
               case 3:
                    popNode();
          }
     }

     public void visitUnaryNode( UnaryNode aNode) {
          switch ( visitNumber ) {
               case 1:
                    visitNumber = 2;
                    break;
               case 2:
                    pushNode( aNode, 3);
                    currentNode = aNode.getChild();
                    visitNumber = 1;
                    break;
               case 3:     
                    popNode();
          }
     }


Doc 29, Flyweight & Memento Slide # 24

public void visitExternalNode( ExternalNode aNode) {
          switch ( visitNumber ) {
               case 1:
                    visitNumber = 2;
                    break;
               case 2:
                    visitNumber = 3;
                    break;
               case 3:     
                    popNode();
          }
     } 

     private void pushNode( Node aNode, int visitNumber ) {
          traversalPath.push( 
               new TraversalStorage( aNode, visitNumber) );
     }

     private void popNode( ) {
          if ( ! traversalPath.empty() ) {
               TraversalStorage nextNode = 
                    (TraversalStorage) traversalPath.pop();
               currentNode = nextNode.getNode();
               visitNumber = nextNode.getVisitNumber();
          }
          else
               visitNumber = 3;
     }
}
Copyright © 1998 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA
All rights reserved.

visitors since 05-May-98