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.
See the UML Diagram at
http://www.burningchrome.com/~cdent/649/ass2/turnin/ass2part1.giffor further details on the interfaces and classes.
Accepts an arbitrary number of instances of appliance objects of a finite set of types. It maintains the following instance variables:
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:
add(Object appliance)
add(Vector appliances)
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)
off(Object appliance)
on()
off()
Defines an interface for executing on and off.
Implements Command. Its receiver is an instance of Blender that it turns on and off.
Implements Command. Its receiver is an instance of Toaster that it turns on and off.
Implements Command. Its receiver is an instance of Oven that it turns on and off.
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.
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.giffor 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.
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.
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:
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.int size = getBytes(); Iterator sizeIterator = getChildren(); while (sizeIterator.hasNext()) { size += ((Node)sizeIterator.next()).getSize(); } return size;
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.
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.
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.
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.
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.
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()
.
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).
See the UML Diagram at
http://www.burningchrome.com/~cdent/649/ass2/turnin/ass2part3.giffor additional detail on the interfaces and classes.
Defines an interface for executing arithmetic expressions (made up of Binary and Unary expressions). The interface is as follows:
public abstract double execute();
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);
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);
public double calculate(ArithmeticExpression expression)
BinaryAddExpression BinarySubtractExpression BinaryModExpression UnaryNegateExpression UnaryAddExpression
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.
(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
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.
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.
There is a UML diagram at:
http://www.burningchrome.com/~cdent/649/ass2/ass2part4.gif
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.
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.
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.
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()
add(Command c)
increment()
decrement()
getCurrent()
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.
A Command that calls reExecute()
on the next command on
the CommandHistory. This
Command never registers itself with the CommandHistory.
Uses the Builder pattern to compose a composite of Commands based on the XML file that is given.
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.