|
Using Patterns in Order Management Systems: A Design Patterns
Experience Report
By Kyle Brown
Abstract
Designing and implementing a new OO system is a challenging task,
especially when the implementors are novice OO programmers. In this
project we turned to design patterns to help design our system,
and to explain the design choices made. Using design patterns helped
us accelerate the design phase of our project, and resulted in a
more easily understandable and better factored design.
I. Introduction
We all know that learning OO design is hard, and that becoming
a good OO designer is harder yet. Teaching novices how to become
good OO designers is one of the toughest problems a person can face.
In the past, we have assumed that only time and experience would
allow a person to learn the hard lessons that of OO design. With
the advent of design patterns, we may now have a medium to make
some of those lessons easier.
In a recent project I mentored a group of OO novices in the development
of a significant application. We used design patterns from the book
"Design Patterns: Elements of Reusable Object-Oriented Software"
() throughout the design process to teach the principles of good
OO design, and to provide solutions to common problems as they were
encountered. While some of the patterns were applied more successfully
than others, I feel that on the whole the method was a success.
II. The Problem
Our particular project was for a major pharmaceutical company's
IS department. Our team was tasked with building an order management
system to be used by employees of the company in placing orders
for consumable resources. The system is intended to allow employees
to order resources by selecting the type, subtype, and vendor of
the resource as well as the delivery date, and various other delivery
details. The user is allowed to change orders after they are placed,
to view the unconsumed resources that are still allocated to him,
and to transfer unconsumed resources to other users.
III. The Constraints
Our design faced several constraints:
We had to deliver a workable application within 3 1/2 months from
the inception of the project.
There was a limited amount of OO and Smalltalk knowledge in our
group. Our team consisted of me, one team member that had 6 months
of OO experience (having been through a KSC Smalltalk Apprentice
Program (STAP)) and three team members with only minimal Smalltalk
training and no formal OOA&D training.
We were required to use an existing Oracle database as our repository.
We were free to use any appropriate object design, but it had to
work with the existing database tables and Oracle Forms applications.
IV. The Solution
As the chief architect of this project, I had two things working
for me as I began. The first was that we had earlier prototyped
a subset of this application in an apprentice program, so we had
a good feel for what objects were involved in the system. The second
was that I had just finished developing a tutorial for Smalltalk
Solutions `95 that drew heavily from , so I was very familiar with
those patterns. The two factors converged to let me begin the design
process by picking out some appropriate patterns that I felt would
be useful in this domain, and then letting the developers discover
how these patterns could be applied to our specific design.
A. The Patterns
From my previous experience in this domain, I knew immediately
that two patterns from would be useful; State and Memento. At the
start of the design process, I described the patterns to the group
and then went on to develop a solution utilizing them. Later in
the design process, problems came up that were well described by
Composite , Mediator, and Adapter as will be shown in sections 4.4
and 4.5. Finally, two rules-of-thumb that were used in this project
were phrased (after the fact) as patterns.
B. State
One of the more common problems found in many MIS systems is the
idea of a workflow. In a workflow objects move from person to person
within a workgroup, with each person changing, annotating, or modifying
the object before it is passed along to the next person. This project
was no exception. The analysis of the project done before the design
phase of the project began described the workflow of the various
types of Orders in some detail. After reviewing this, I determined
that the workflow could be described as a state machine, with different
submissions and modifications of the order describing the transitions
between states. The states Orders can occupy are shown in Figure
1.

Figure 1: Order States
Once we had determined to represent workflow of an order as a finite
state machine, the design of a significant part of our Order object
"fell out" of the State pattern. We determined that an
Order could be in several different states, depending upon where
within the workflow it resided. We also determined that the Order
should behave differently to the common messages save, delete, and
notify depending upon its state. For example, when an order was
in Submitted state, it was known (so far) only to the person placing
the order; the buyer (the next person in the workflow) had not yet
reviewed it. A delete message sent to the Order should physically
remove it from the database when it is in this state. On the other
hand, if an Order is in Ordered state, then a delete message should
only log the fact that that particular order has been removed. A
deletion in this state will necessitate the buyer resubmitting a
corrected VendorOrder to the vendor.
Likewise, when an Order is in New state, the buyer should be notified
(by E-Mail) when the Order receives the submit message and moves
to Submitted state. A different notification should be sent out
when a change is made to an Ordered order. Once an order is in ChangedOrdered
state, no more notifications are necessary.
We were able to use the following design to represent the state
machine portion of our domain model (see Figure 2: State design).
Just as described in , we used an abstract class State that implemented
the messages notifyWith :, saveWith : and deleteWith :, the argument
to each of these messages being the ConsumablesOrder. Each of the
messages in ConsumablesOrder that differed by state were implemented
by calling the corresponding message in the Order's current state.
For example, let's look at the following implementation:
(ConsumablesOrder>>delete)
delete
"do whatever is appropriate for your current state"
self currentState deleteWith: self.
(SubmittedState>>deleteWith:)
deleteWith: aConsumablesOrder
"tell your consumables order to remove himself"
aConsumablesOrder remove.
(OrderedState>>deleteWith:)
deleteWith: aConsumablesOrder
"tell this consumablesOrder to become deleted (i.e. record
the fact in the database)"
aConsumablesOrder becomeDeleted.
(DeletedState>>deleteWith:)
deleteWith: aConsumablesOrder
"anOrder is already deleted. Do nothing"
^self

Figure 2: State design
The only substantial difference from our design to the design from
was the addition of a StateMachine object between the ConsumablesOrder
(the Context) and the State. In retrospect, we could probably have
done without this object. It was only used to aid in construction
of the State connections, and for error handling in the case where
a transition wasn't defined in the current state. This responsibility
could easily have been absorbed into the abstract State class.
The State pattern was the big success story in this application.
Its use cut through a lot of complexity in the domain that would
otherwise have been handled by several conditional branches spread
throughout the code. The pattern was easy to explain to the developers,
and the implementation was quick and painless. It proved to be easily
extensible (when we began implementing we only knew about deleteWith
: and notifyWith : -- saveWith: was added later) and flexible.
C. Memento
Going into the design of this application, we were aware that we
would need to support one-level undo for a ConsumablesOrder. For
instance, if a buyer rejects a change to an Order, then the order
should revert back to the state it was previously in (Ordered) and
all of the changes should be erased. We felt intuitively that the
correct solution would be a variant of the Memento pattern, and
we discussed possible implementations during the early design sessions,
starting with the design example presented in . However, in contrast
to how easily we adopted the State pattern, adopting Memento proved
to be more challenging.
We were a bit confused by the Caretaker object in the pattern being
external to the Originator object, although reviewing it further
did clarify it a bit. In our case, there were no external clients
of the Originator that needed to know about the existence of a memento,
and only one copy of the memento needed to exist at any time. We
finally assumed that this was a degenerate case of Memento not covered
in . Our resulting design is shown in Figure 3: Memento class structure.

Figure 3: Memento class structure
We rolled the Originator and Caretaker objects into a single object,
the ConsumablesOrder. The basic flow of messages, and the structure
of the classes, are the same as in once this change is made. When
an outside object sends a message to a ConsumablesOrder that would
change its state, it issues itself a makeMemento message. This creates
the memento and sets its state appropriately (this is done all at
once by a deepCopy message). Whenever an outside object sends a
message to a ConsumablesOrder that would necessitate reverting back
to its original state (such as cancelChanges) it sends itself the
revert message, which resets the state to the stored previous state
by copying all of the values of the instance variables in the memento
back into the original order.
This case was unique in that the Memento pattern did no provide
us the solution directly, but led us to an acceptable solution that
fit our requirements. Even though the particular solution provided
by the pattern's example code didn't work for us, the thought process
we went through in trying to use the pattern did lead us to an acceptable
solution.
D. Composite
- After getting more deeply into our design and implementation,
we realized that an unforeseen client requirement was easily solved
by application of another pattern, Composite. In our initial design
we identified three subclasses of the class Resource:
- OrderResource -- this represents a resource that has been ordered,
but not received. It is sort of a "virtual" resource,
and doesn't share many of the attributes (disposition, receivedDate,
etc.) of an "actual" resource.
- IndividualResource -- this represents a specific, received resource
of a certain type. Some resources are tracked individually, with
specific ID numbers. A Chair, or a Forklift, or something of this
sort is an IndividualResource.
- GroupedResource -- a grouped resource is a set of resources
that are not uniquely identifiable. For instance, a bag of bolts
might be considered a GroupedResource in that it contains several
bolts, but each bolt is not important enough to represent individually.
However, the entire bag is interesting enough to track.
In our original design, the user was shown a list of IndividualResources
and GroupedResources that were allocated to them. In subsequent
user interviews, it came to our attention that the users would prefer
to see all of the IndividualResources that were ordered from the
same order as a single line item in this list, then drill-down to
see the Individual resources. After some consideration, we decided
the easiest way to achieve this was to use the Composite pattern,
and refactor the hierarchy to create some new classes. First, we
divided Resource into two classes, AbstractResource, which defined
a resource's protocol, but not its implementation, and ActualResource,
which defined the implementation used by the preexisting Resource
subclasses. We then defined one more class:
- CompositeResource -- a CompositeResource is a subclass of AbstractResource
that responds to the same protocol as an ActualResource, but which
is implemented quite differently. It contains a collection of
IndividualResources, and implements its protocol by passing through
many of its messages to a representative element of that collection.
A CompositeResource can answer its type, subtype, etc. just as
can an instance of a subclass of ActualResource.
The full Resource hierarchy is shown in Figure 4: Resource Hierarchy.
The great thing about using Composite was that our user interface
code did not change at all when we refactored the hierarchy. Since
a CompositeResource responded to the same protocol as an IndividualResource
or a GroupedResource, the display logic was identical. We were able
to easily add new drill-down capabilities through additional UI
code that was specific to CompositeResources.
Figure 4: Resource Hierarchy
An important lesson learned through the application of this pattern
was that the interface of an object is different than its implementation.
One of the programmers really struggled with why we were refactoring
the hierarchy and separating the interface (in AbstractResource)
from its implementation (in CompositeResource and ActualResource).
The "light came on" in this programmer's mind after we
had roughed out the first iteration of code for the new hierarchy
and then started up the user interface without having modified any
UI code. This was a key lesson in OO design in that for the first
time the programmer realized what it meant for a class to be abstract,
and why abstract superclasses were useful.
E. Mediator and Adapter
Our use of these two patterns was more simplistic than the other
patterns. In our target language, Smalltalk/V, the ViewManager class
provides a Mediation interface between its component SubPanes. It
also serves as an Adapter between the SubPanes and the objects of
the domain model . The two patterns were used more in spirit than
in fact. Whenever any code was written in a ViewManager subclass
it was carefully reviewed to see if it fulfilled either the role
of Mediator or Adapter. Any code that attempted to do something
other than coordinate SubPane display, or adapt SubPane events to
domain model methods was rejected in the code review as violating
our rules. As an example, at one point a programmer was planning
to place some Unit of Measure conversion code in a specific ViewManager.
After a code review, she agreed that this was neither mediating
between SubPanes, nor adapting to the domain model. She then developed
a more general UnitOfMeasure class for handling the conversions,
and wrote only enough code in the ViewManager to adapt this class
to the input and output methods of the SubPanes. This allowed her
to extend the UnitOfMeasure class to handle similar, but unforeseen
cases later in the project without changing the ViewManager code.
V. Other Patterns
In developing this system, there were two more "rules of thumb"
that we followed during the design, that, while not in pattern form
at the time of the development, were patternizable after the fact.
Each solution had all the earmarks of a pattern:
- It was a solution to a general problem within a set of constraints
- It had been used several times in other projects
- It was easily explainable in a few sentences
All that remained was for the solution to be written in pattern
form. The description of the heuristics we used follows. I have
since rewritten them in pattern form, and used them as part of "Crossing
Chasms" a pattern language for object to relational database
interface design.
A. Errors as Objects
In a previous project I had seen an interesting way of separating
concerns in the ViewManager classes from domain-layer considerations
with respect to errors. In this approach, domain validations (range
checks, type checks, etc.) were done in the domain, and the results
were passed back to the ViewManager as an ErrorSet. In this way
you could distribute the responsibility for validation among several
objects, with the error set being passed around and added to whenever
a validation failed. This design preserved model and view separation,
and allowed the user to intervene in the handling of recoverable
errors.
When the top-level message returned, the ErrorSet was displayed
by the UI, and each Warning (which represents a potentially recoverable
error) was flagged as to whether or not it was proceedable. The
entire ErrorSet was then passed back to the domain, which used it
to determine if it should allow the next action.
B. Broker
A second design heuristic that we used was the concept of a Database
broker. A Broker acts as an Adapter between a persistent domain
object and the classes that represent the physical database and
the query language. It translates "domainish" requests
into "databasish" queries and helps in mapping SQL rows
and columns to objects and instance variables. It provides a needed
separation of concerns that isolate the domain classes from the
purely database-oriented classes. This architecture allowed us to
meet our requirement that we use the existing Oracle tables, while
at the same time freeing us to use a fully OO design in our domain
classes.
While these solutions were not written down as patterns when we
were designing our system, I nevertheless presented them to the
developers just as I had presented the patterns from . This process
of explaining them in this way helped immensely when I sat down
to write them in pattern form later.
VI. Summary
Looking back on the project, I feel very strongly that our approach
to teaching OO design through the use of patterns worked. We were
able to solve many of the common problems in our domain through
the use of patterns, and we were able to explain our specific solutions
within the context of a more general solution. The communications
medium of patterns made it much easier for outside reviewers to
understand our design than would otherwise have been possible. At
the end of my contract, when I turned over the project to another
KSC mentor, I was able to summarize the pages of design notation
that we had generated into a few sentences about the patterns we
used and where we used them. In less than an hour I was able to
pass on the "big picture" of the whole system to another
person and have them understand enough about it to enable them to
ask detailed and specific questions without being overwhelmed by
the volume of documentation.
As a result of the success in teaching OO design with patterns
in this project, I have begun incorporating patterns directly into
the Smalltalk Apprentice Programs that I teach. Early results with
the first two STAPs indicate that the patterns are often considered
the most useful part of the design portion of a STAP.
References
[Brown95a] Kyle Brown, " Remembrance of Things Past: Layered
architectures for Smalltalk Programs ", The Smalltalk Report,
July/August 1995.
[Brown95b] Kyle Brown and Bruce Whitenack, " Crossing Chasms
-- A Pattern Language for Object-Relational integration", submitted
to the PLoP (Pattern Languages of Programs) `95 conference , Sep
6-8, 1995, Allerton Park, Monticello Illinois.
[Gamma95] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides,
" Design Patterns: Elements of Reusable Object-Oriented Software
", Addison-Wesley, 1995.
Footnotes
1 [Gamma95] p. 305
2 [Gamma95] p. 283
3 [Gamma95] p. 163
4 [Gamma95] p. 273
5 [Gamma95] p. 139
6 [Brown95a]
7 [Brown95b]
|