Design Patterns Assignment 2
Mary Clegg and Chris Dent
September 16, 2002

  1. PowerSwitch
  2. FileManager
  3. Calculator (the problem we implemented)
  4. Framework

Resources


Part 1: PowerSwitch

Overview

A flexible PowerSwitch that can turn any number of types of objects of classes representing electrical appliances on and off. The appliance classes already exist and cannot be modified because other clients are dependent on these classes' interfaces. Our solution implements the Command pattern to provide a unified interface for turning appliances on and off. PowerSwitch can take appliances one at a time or as a collection of objects. However, it currently only supports already existing appliances. It's interface is flexible and therefore the addition of new appliances in the future requires no change to the PowerSwitch's interface. It would however require modification to the PowerSwitch's internal definition.

The appliances in question have different methods for on and off actions. For example, a Blender has swizzle() to start it "swizzling". The implementation of BlenderCommand's on() method would called swizzle() on the Blender object to which it has a reference.

Interfaces and Classes

See the UML Diagram at

http://www.burningchrome.com/~cdent/649/ass2/turnin/ass2part1.gif
for further details on the interfaces and classes.

PowerSwitch

Accepts an arbitrary number of instances of appliance objects of a finite set of types. It maintains the following instance variables:

  1. a Map, mappings of the apriori set of apppliance Class to Command Class
  2. a Vector commands, a list of intance variables created based on client add reqeusts

An ApplianceNotSupportedException is thrown if a client attempts to add an appliance that the PowerSwitch doesn't know about.

PowerSwitch supports the following methods for accepting new instances of appliance objects:

When an appliance is added to the PowerSwitch the PowerSwitch calls it's Class map to create the appropriate type of Command instance and sets the given appliance as its receiver; it then adds this Command object to its list of commands. PowerSwitch also has:

on(Object appliance)
turns given appliance on
off(Object appliance)
turns given appliance off
on()
turn all on
off()
turn all off

Command

Defines an interface for executing on and off.

BlenderCommand

Implements Command. Its receiver is an instance of Blender that it turns on and off.

ToasterCommand

Implements Command. Its receiver is an instance of Toaster that it turns on and off.

OvenCommand

Implements Command. Its receiver is an instance of Oven that it turns on and off.

Client

Creates an arbitrary number of instances of Blender, Toaster, and Oven objects and adds them to PowerSwitch. It can then turn those instances on and off by making the appropriate request on the PowerSwitch.


Part 2: FileManager

Overview

The FileManager class uses an implementation of the Composite pattern to provide an interface to a hierarchy of files and directories. The Composite pattern makes it possible to manipulate both files and directories using, for the most part, the same commands. The differences occur when reading FileNodes or performing actions inside but not upon DirectoryNodes (these actions are explained below in the FileNode and DirectoryNode class descriptions).

Many operations can fail for a variety of reason: file not found, permissions don't allow the operation, etc. In those cases exceptions (not described here) are thrown and should be caught by the client that implements FileManager.

The file hierarchy represented is quite rudimentary. For example, performance is poor, as FileNodes and DirectoryNodes are found by traversing the hierarchy and no caching is provided.

See the UML diagram at:

http://www.burningchrome.com/~cdent/649/ass2/ass2part2.gif
for a more complete list of methods. Poseidon does not easily allow the placement of a line that indicates an association between DirectoryNode and Node somewhere other than on the arrow that indicates inheritance, however there is an association there indicating that DirectoryNode keeps a Vector of Nodes.

Interfaces and Classes

Node

Defines an interface to a hierarchy of Nodes that represent entities that might be found in a filesystem. Acts as the Component participant in the Composite pattern.

Details of the abstract methods on this class can be found in the UML diagram and in the description of the FileSystemNode class below.

FileSystemNode

An abstract class (to prevent instantiation) implementing the Node interface. As most methods and attributes, such as rename() and getName() are identical between the DirectoryNode and FileNode classes, those are implemented on this class.

getChildren() provides the mechanism by which the Node hierarchy may be traversed. A search() method is provided to find matches in the hierarchy.

Each FileSystemNode has a reference to its parent. This is necessary when a child reference needs to be removed from the list of children contained on the parent. This is similar to the ".." entry in a Unix directory.

Some methods are recursive: getSize() will get the size of a node and all its children. Others are not: getName() returns the String name associated with a single FileNode or DirectoryNode.

Recursion is accomplished in getSize() as follows:


    int size = getBytes();
    Iterator sizeIterator = getChildren();
    while (sizeIterator.hasNext()) {
        size += ((Node)sizeIterator.next()).getSize();
    }
    return size;

The constructor for Node requires a String name, or a Node parent and String name. Without a Node it is assumed to be a root of a filesystem.

FileNode

A subclass of FileSystemNode representing the Leaf participant in the Composite pattern. It is a file. FileNode:getChildren() returns an empty Iterator. A nice way to do this would be to create a single empty Iterator and let each FileNode use it.

A FileNode also includes read() and other methods for accessing or executing content.

DirectoryNode

A subclass of FileSystemNode representing the Composite participant. It is a directory and may contain 0 or more FileNodes or DirectoryNodes in its children.

DirectoryNode includes special methods, such as listDir() and deleteAll(), that perform actions on the children they contain, but not themselves. In the case of deleteAll(), remove() is called on all the children by iterating with getChildren(). Calling remove() on the DirectoryNode would remove() all the children and the DirectoryNode as well (see below).

DirectoryNode implements add(Node) for adding children. FileNode does not.

FileManager

A class providing an interface to the hierarchy of Nodes. FileManager client code calls methods on the Nodes to which it has access. getNode() implements the code required to start a search() from a given Node to locate a Node object on which an action is to be performed.

Filemanager contains public methods that use Strings to indicate files and directories. These map to private methods that take Node arguments found by the search() method. For example makeFile() maps to createFile() (which eventually calls add() in the Node, found by search(), in which the file will be contained)

The createFile() and createDirectory() methods are explained in more detail below.

Issues

What happens to create a file?

Creating a FileNode or DirectoryNode means adding a FileNode or DirectoryNode to the Node hierarchy. The proper location where it will be added must be found. The following process is used:

The client calls FileManager:makeFile() with a string argument, the fully qualified pathname to be created. This calls FileManager:createFile() called with two arguments: a Node and a string representing a filename. The Node object is a starting point in the tree (generally the root of the tree). search() is called (described below) to find the Node where the new file should be added.

The path is a map through the tree. When the final DirectoryNode is reached, the FileNode is add()ed.

If the path does not resolve, an exception is thrown.

How does searching work?

There are several possible implementations. One is: Node:search() is passed a Vector of Strings and an index into the Vector. The Vector represents a pathname that has been tokenized on the path separator. search() returns a Node when the string matches and the end of the list has been reached, throwing a file not found exception as its base case. The index is increased with each recursive call. search() uses getChildren() to search the current node's children for a node with a name that matches the current string in the list.

A better way would be encapsulate the pathname as a class.

What happens when I remove() a Node?

If the Node hiearchy only represented a data structure remove() could remove a DirectoryNode, pruning it and its children from the tree. The garbage collector would take care of the objects. We don't do this because we want to guarantee that Nodes are removed in a known order and we need to clean up the parent. Therefore remove() removes all children and then deletes the reference to itself in its parent.


         Iterator childrenIterator = getChildren();
         while(childrenIterator.hasNext()) {
                ((Node)childrenIterator.next()).remove();
         }
         getParent().remove(this);

remove() with a Node argument removes that Node from the list of children. Attempting to get the parent of the root node throws an exception.

Because finalize() on a Node can not be guaranteed we perform the dirty work for physical cleanup in the filesystem in remove().


Part 3: Calculator

Overview

A Calculator that takes an ArithmeticExpression and computes it value without having to parse a String representation of the expression. The ArithmeticExpression is a Composite of Command objects. The execute method of each Command object evaluates the arithmetic expression it represents and returns its value. The ArithmeticExpresion can be built using the parameterized constructors and modified by using the public getters and setters. A BinaryExpression defines an interface for objects that wish to represent binary arithmetic expressions (composite) and a UnaryExpression defines an interface for objects that wish to represent unary arithmetic expressions (leaf).

Interfaces and Classes

See the UML Diagram at

http://www.burningchrome.com/~cdent/649/ass2/turnin/ass2part3.gif
for additional detail on the interfaces and classes.

ArithmeticExpression

Defines an interface for executing arithmetic expressions (made up of Binary and Unary expressions). The interface is as follows:


      public abstract double execute();

BinaryExpression

Defines an interface for binary expressions. A BinaryExpression is a node in the tree that has other ArithmeticExpressions as its children; it represents a binary arithmetic expression (e.g. 20/5). The interface defines the following abstract methods:


      public abstract ArithmeticExpression getRight();
      public abstract ArithmeticExpression getLeft();
      public abstract void setRight(ArithmeticExpression right);
      public abstract void setLeft(ArithmeticExpression left);

UnaryExpression

Defines an interface for unary expressions. A UnaryExpression is a leaf in the tree that represents a unary arithmetic expression (e.g. -4, +10, or ~3). Interface defines the following methods:


      public abstract double getLeft();
      public abstract void setLeft(double left);

Calculator

Implements one method:

      public double calculate(ArithmeticExpression expression)

Example implementation classes


    BinaryAddExpression
    BinarySubtractExpression
    BinaryModExpression
    UnaryNegateExpression
    UnaryAddExpression

Issues

What happens when Calculator.calculate(ArithmenticExpression expression) is called?

When calculate() is called execute() is called on expression. execute() in turn calls execute() on all Binary or UnaryExpression that comprise the ArithmeticExpression.

Sample Output from implemented code

(7 + 5) / (3 + 4) * (10 % 12) = 17.142857142857142
(12 + 15) / (3 + 4) * (10 % 12) * 30 = 1157.142857142857
-3 = -3.0
(1 + 2) + (3 + 4) = 10.0
(1 + 2) + 3 = 6.0

Part 4: Framework

Problem Description

Develop a collection of classes that facilitate creating a hands-free demonstration of the new interface with which some marketing types want to impress some customers. Implement a system that reads an XML file describing a series of actions to be performed on the components that make up the interface and then performs them. Include infinite undo and redo capability so that the customers are suitably impressed. Undo and redo is implemented as suggested in descriptions of the Command pattern, rather than as a series of commands described by the XML file as the latter would violate the spirit of the exercise.

Overview

The solution to this problem implements both the Composite and Command patterns. The Command pattern is used to make the interface actions available to the demoing system in a consistent fashion as well as allow for the use of undo. The XML file used for input describes a series of events. Some of these are obvious Composites of Commands (for example 'move Slider Y's grabber 4 pixels left' is a Composite of 4 'move Slider Y's grabber left' Commands). In addition, some of the commands described by the XML file should be considered single commands when an undo request is initiated (for example, enter 'hello' in EditBox Z is both a multi-step action as well as something that should undo with one undo).

Although we won't go into to detail regarding parsing, our solution is to accept an XML file that would be used by a CompositeBuilder class that uses the Builder pattern (GoF, p. 97) to create a broad hierarchy of Commands. Collections of actions which should be undone in one lump or are repetitions of the same action are nested below a container in the XML file.

The CompositeBuilder reads in the XML file and creates a hierarchy of MacroCommands containing Commands. The type and attributes of the Commands are based on the text in the XML file. For example one string may be associated with the Receiver of the Command, the other with the Command itself, and another with whether this command should be "undoable", as follows:: <command receiver="ButtonX" action="pressButton" undo="yes">.

The undo attribute allows the XML file to control which Commands or MacroCommands save themselves on the CommandHistory. Its presence sets a flag on the Command. A MacroCommand that has undo set, should not contain Commands that have undo set, or history tracking will go awry.

The order and nesting of the XML file defines the structure of the Composite hierarchy. Nested Commands are contained in MacroCommands (see p. 235 of _Design Patterns_).

Once the collection of commands has been assembled they can be executed by calling execute() on the MacroCommand at the top of the hierarchy.

Supporting undo proceeds as follows: as each Command execute()s, if the undo flag is set, a reference to the Command is put on a CommandHistory. All Commands, however, implement unExecute() to support unExecute() on the children of MacroCommands that put themselves on the CommandHistory.

When undo is called, unExeute() is called on the current Command on the CommandHistory. Each Command maintains enough state to undo (or redo) itself.

Undo and Redo are themselves Commands in the command hierarchy. Undo and Redo Commands do not register themselves on the CommandHistory list. When called, they call unExecute() or reExecute() on the current or next command (respectively) on the CommandHistory and then manipulate the pointer to the current Command on the list appropriately. Undos and Redos can be aggregated into a MacroCommand just like other commands.

Interfaces and Classes

There is a UML diagram at:

http://www.burningchrome.com/~cdent/649/ass2/ass2part4.gif

Command

Defines the execute(), unExecute() (for undo), and reExecute() (for redo) interfaces.

reExecute() is necessary for those cases where a redo is more complicated than simply calling execute() again.

MacroCommand

A Composite of command. When execute(), unExecute() or reExecute() is called the corresponding method is called on all of its children. The iterator used must be a ListIterator() so unExecute() can traverse the commands in reverse.

Implemented Commands

The large number of action classes that implement the Command interface. A Receiver reference is set upon creation. When execute() is called the methods necessary to make Receiver do the desired actions are called. In most cases this will be the firing of some event such as a button press, or key press. The details of the commands on the Receivers is not explained here, as we don't have access to the interface.

When execute() is called, if the undo flag is set, this command is add()ed to the CommandHistory. The Command calls CommandHistory.getInstance() to gain access to the CommandHistory.

unExecute() reverses the actions in execute. In some cases this could be a fairly complex series of actions. In others simple. In some cases unExecute() is impossible and an exception should be thrown to stop the undo process (if there are a chain of multiple undos).

To keep the demo slow enough so a human can "enjoy" the view there need to be occassional Thread.sleep()s. This can either be done by putting sleep()s in the implemented commands or having a specific Command that performs a certain amount of sleep()ing and aggregating those commands in the XML file. Such a command should not register itself with the CommandHistory.

CommandHistory

Uses the Singleton pattern (GoF, p. 127) to ensure that only one instance exists.

CommandHistory is essentially a doubly linked list that holds Commands and provides the following methods for maintaining the list of commands and supporting undo and redo.

getInstance()
static method that returns the instance of the CommandHistory
add(Command c)
adds Command to the right of current and then calls increment()
increment()
moves current reference to next node (right of current)
decrement()
moves current reference to previous node (left of current)
getCurrent()
returns a copy of the Command at the current location

UndoCommand

A Command that calls unExecute() on the current command on the ComandHistory and decrements the CommandHistory pointer. This Command never registers itself with the CommandHistory.

RedoCommand

A Command that calls reExecute() on the next command on the CommandHistory. This Command never registers itself with the CommandHistory.

CompositeBuilder

Uses the Builder pattern to compose a composite of Commands based on the XML file that is given.

Framework

The class that sets up the interface, causes the XML file to be read in and the Commands to be created with their Receiver objects by the CompositeBuilder. Starts execution of the demo by calling execute() on the MacroCommand at the top of the hierarchy. Deals with exceptions that might be thrown from Commands.

Depending on the interface being demoed, this class most likely extends JFrame to provide a container for all the components that make up the interface.