Services


Smalltalk Courses






- Mentoring and Apprenticeships
- Project Development
- Open Enrollment Courses

Contact sales@ksc.com or call 800 348-8323 to discuss your options.


- Why Smalltalk
- STIC
- Smalltalk Solutions



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]

 

 

 



Smalltalk/JVM
Smalltalk to Java in no time flat!

A new version will be coming soon. For more information visit Mission Software




Mission Software


Knowledge Systems Corporation
1143 Executive Circle Suite G
Cary, NC 27511


company - services - site map - contact - home


Copyright © 2004 - Knowledge Systems Corporation
All Rights Reserved.