Patterns at work
In this section and the two that follow we'll uncover, evaluate, and use several design patterns to build a Web-based application. This application will allow users to order movies from a Web site. We'll use four design patterns to build the application: two business patterns, one Web presentation pattern, and one J2EE pattern. We'll start with a look at the business patterns.
Patterns specific to problems in vertical business applications can be hard to find. Fortunately, two books currently on the market, Patterns for e-business: A Strategy for Reuse and SanFrancisco Design Patterns: Blueprints for Business Software, focus almost entirely on business patterns. (See Resources for details on these titles.)
The business patterns each book describes vary considerably in terms of granularity. The first, Patterns for e-business, presents coarse-grained architectural patterns. These patterns are geared to helping you choose a path for structuring and architecting an e-business system. Because these patterns are architectural in nature, we won't implement any of them here. You might benefit from further study of these patterns, however, with particular attention to how the architectural patterns differ from a lower-level design pattern.
The patterns found in SanFrancisco Design Patterns: Blueprints for Business Software are lower-level design patterns, structured similarly to the GOF patterns. Two of these patterns, the Property Container pattern and the Simple Policy pattern, will be the basis of our movie-ordering application.
The Property Container pattern
Property Container is a foundational pattern. Its function is to ensure that an application, once built and deployed, can be dynamically extended.
Consider the movie-ordering application. When designing a class to represent a movie, it's unlikely that you would concern yourself with all the attributes to describe a movie. But as a conscientious designer, you would want to ensure that the movie class could be extended with additional data as needed. Some extension mechanisms require a programmer to recompile the code and redeploy the classes to be extended. Property Container, on the other hand, gives us a mechanism to dynamically extend an object with additional attributes at run time. Of course, creating the Property Container is only the beginning; the application may also require modules that explicitly take advantage of the new property once it's been added.
A variety of mechanisms exist for making the Movie class a
Property Container. Ours will be a fairly simple approach: we'll create a
standard interface for the Property Container, then extend or implement that
interface for all interfaces and classes that we want to be able to modify at
run time.
In the next several sections we'll design a Property Container interface, then extend that interface to define the Movie interface that is the basis of our movie-ordering application. We'll also work with the concrete class implementations for the Property Container and Movie interfaces.
Designing a Property Container
In the following UML class diagram, notice that we have created a very simple Property Container interface and an abstract implementation from which classes can inherit:
As you can see, there are four methods to add, remove, and query properties
in the Property Container. The properties are identified by a
String key. In the concrete implementation we'll simply use a
hashtable to store the properties as they're added to the Movie
class.
We're working with a very simple Property Container design and implementation here, but the actual pattern has some critical extensions. These extensions illustrate the different ways the pattern can function in a more robust and extensive implementation. For example, if an object is in a hierarchy of run-time objects, the Property Container pattern allows for the traversal of that run-time hierarchy.
One example of a run-time hierarchy is a department contained within another department, which in turn is contained within a company. A query against a department's dynamic properties for an accounting code would make use of the hierarchical Property Container. The Property Container pattern would define the behavior for traversing the containment hierarchy for the specific property of the accounting code.
Implementing a Property Container
Our implementation of the Property Container will be very straightforward. To get started, take a look at the Property Container code below:
public abstract class PropertyContainerImpl
implements PropertyContainer, Serializable
{
protected Hashtable ivProperties = new Hashtable(2);
public PropertyContainerImpl() {
}
/**
* Add a property associated with a token name.
* If the token already exists, the value will be replaced.
* If the token does not exist, it will be added with the value.
* @param value is an object that cannot be null
* @param token is a key that can be used to retrieve the value
*/
public void addPropertyBy(Object value, String token) {
if(value==null || token==null) return;
if(ivProperties.containsKey(token)){
ivProperties.remove(token);
}
ivProperties.put(token, value);
}
/**
* Retrieve a value by a particular token.
* @param token is a key that can be used to retrieve the value
* @return Object is the value associated with the token. It
* will not be null.
*/
public Object getPropertyBy(String token) {
if(token==null) return null;
return ivProperties.get(token);
}
/**
* Retrieve all property keys currently in use.
* @return String[] is an array of all valid token names.
*/
public String[] getPropertyKeys() {
String keys[] = null;
synchronized(ivProperties){
int s = ivProperties.size();
keys = new String[s];
Enumeration e = ivProperties.keys();
int i = 0;
while(e.hasMoreElements()){
keys[i] = (String)e.nextElement();
i++;
}
}
return keys;
}
/**
* Remove a value associated with a particular token.
* @param token is a key associated with a value that was added
*/
public void removeProperty(String token) {
if(token==null) return;
ivProperties.remove(token);
}
}
|
As you can see, our Property Container uses a Hashtable to store property values. The key is determined by the person adding the property. You will typically want to standardize the keys in some way, such as the package name and class responsible for adding the property.
The next step is to use the Property Container interface and abstract class
implementation to implement the Movie interface. We'll use classic, simple
JavaBean patterns to devise the properties for our Movie interface
implementation. The implementation, MovieImpl,
will inherit from
the abstract PropertyContainerImpl implementation in the previous
panel.
public class MovieImpl extends PropertyContainerImpl
implements Movie, Serializable
{
private int available;
private String description;
private float price;
private String rating;
private String title;
private String id;
public MovieImpl() {
}
public int getAvailable() {
return this.available;
}
public void setAvailable(int available) {
this.available = available;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public float getPrice() {
return this.price;
}
public void setPrice(float price) {
this.price = price;
}
public String getRating() {
return this.rating;
}
public void setRating(String rating) {
this.rating = rating;
}
public String getTitle() {
return this.title;
}
public void setTitle(String title) {
this.title = title;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
|
You should find no surprises in our MovieImpl class
implementation. Recall that all the behavior for the Property Container is
actually contained in the PropertyContainerImpl superclass. The
remainder of the code is simply JavaBean patterns, which fill in the primary
attributes of the MovieImpl class.
After creating several Movie objects and placing them in a
vector, we may find that some of the movies have additional attributes. For
example, one of the movies may not be released yet. Rather than extending the
class, we can add the releasedate property using the inherited
addPropertyBy method on the Movie class, as shown
here:
Movie temp = (Movie)movies.elementAt(1);
temp.addPropertyBy(
new GregorianCalendar(2002, 5, 22),
"releasedate");
|
A test program that populates two Movie objects and
adds a release date property is in the package com.stereobeacon.patterns.business.propertycontainer.
To run the
program, use the com.stereobeacon.patterns.business.propertycontainer.TestPropertyContainer
class.
Pros and cons of the Property Container pattern
There are some disadvantages to using the Property Container pattern. With the off-the-shelf property container implementation, you lose strong typing. Also, the interface to the class is not entirely descriptive of the contents, and you'll probably have to modify a user interface to take proper advantage of the added attribute. If you're using the method of serialization to a database, the Property Container itself could cause some headaches as well.
Despite the drawbacks, the Property Container pattern is well suited for certain types of applications, especially when coupled with the ability to traverse a containment hierarchy.
We'll put the Property Container aside for now, as we concentrate on integrating another essential pattern into our application.
The Simple Policy pattern is similar to the GOF's Strategy design pattern, but specialized to deal with the realities of business. The Simple Policy pattern allows us to dynamically set and enforce access policies for our application.
For example, say that an online movie store wants to enforce a policy to restrict the sale of movies to minors, based on movie ratings. The store executives haven't settled on a single policy, however, so our movie-ordering application has to be flexible enough to change policies with ease. Furthermore, movie ratings occasionally change, so the application must be able to take into account new ratings scales or criteria when an order is placed. All of this means that the rules for validating that a particular customer is old enough to order a particular movie could be considered volatile code. Rather than having to recompile new code every time the store policy or industry ratings system changes, it would be easier to isolate the volatile code and allow it to change dynamically. This is essentially what the Simple Policy pattern does.
The Order class contains the volatile validation logic. This
logic is extracted out into concrete classes that implement a single interface.
In our case, the interface is named AgeValidationPolicy and there
are two different policy implementations, as follows:
-
AgeValidationPolicyDefaultfollows the U.S. movie rating recommendations (for example, a PG-13 movie cannot be sold to anyone under age 13). -
AgeValidationPolicyLoosesays that anything up to and including R-rated movies can be purchased by individuals of any age.
In addition to the policy classes, we have an Order object
that contains a customer and one or more movies, as shown below:
Not shown in the diagram is a simple registry, which implements the GOF's
Singleton pattern (see Resources ). To keep things simple, the
registry is a concrete implementation of the Property Container pattern. When
an order is created, the Order object locates the
validation policy in the registry, then passes the customer information and movie order to the
validation policy. The policy throws a run-time error if the customer is unable
to purchase the movies due to a policy violation.
The run-time registry is convenient because it lets us change the validation policy on the fly. So, if the store executives knew that the store's sales were going to be audited by a movie watchdog group, they could run the default policy. Once the audit was complete, they could replace the default policy with the "loose" policy, thus increasing sales. Integrating the Simple Policy pattern into our application ensures that policy changes don't require recompilation, just that the new policy be registered in the run-time registry.
The interesting portion of the Order class is where the
validation occurs. To demonstrate how the validation policy works,
we'll look at the constructor on the concrete Order implementation:
/** Creates a new instance of OrderImpl */
public OrderImpl(Customer customer, Movie[] movies) {
AgeValidationPolicy policy =
(AgeValidationPolicy)
PolicyRegistry.getInstance().getPropertyBy(
AgeValidationPolicy.Token
);
if(policy==null) policy = new AgeValidationPolicyDefault();
policy.validatePurchasingAge(customer, movies);
this.customer = customer;
this.movie = movies;
}
|
Upon attempting to create an instance of an order, the constructor retrieves
the Singleton policy registry and gets the validation policy by a predefined
token. If no validation policy is registered, the default policy is used. We
then call the validatePurchasingAge method on the policy. If the
policy fails, it returns a run-time exception and the constructor will
also fail. Otherwise, we go ahead and set the customer and movie array.
Once we're in the order constructor, we assume that someone is maintaining
the registry with the age validation policy. The test program
com.stereobeacon.patterns.business.simplepolicy.TestSimplePolicy
demonstrates the run-time exchange of policies. To run the demo, we first add
the default age validation to the registry. Once added, the default
policy will be the one retrieved by the AgeValidationPolicy.Token
(the token is simply the string "com.stereobeacon.patterns.business.simplepolicy.AgeValidationPolicy").
public static void main(String[] args) {
PolicyRegistry.getInstance().addPropertyBy(
new AgeValidationPolicyDefault(), AgeValidationPolicy.Token
);
Movie[] movies = createMovies();
Customer customer = new CustomerImpl();
customer.setAge(17);
customer.setName("Paul");
try {
Order o = new OrderImpl(customer, movies);
System.out.println("Successful Order Creation");
} catch(ValidationException exception){
System.out.println("Could not create Order due to Validation Exception");
System.out.println("Message is: "+exception.getMessage());
}
PolicyRegistry.getInstance().addPropertyBy(
new AgeValidationPolicyLoose(), AgeValidationPolicy.Token
);
try {
Order o = new OrderImpl(customer, movies);
System.out.println("Successful Order Creation");
} catch(ValidationException exception){
System.out.println("Could not create Order due to Validation Exception");
System.out.println("Message is: "+exception.getMessage());
}
}
|
Once the policy is added, we populate some movies and create a 17-year-old customer, Paul. Clearly, our customer should not be able to order R-rated movies. Upon creating the first order, we'll receive a validation exception. Next, we change the registry and use the loose validation policy, which allows anyone to order R-rated movies. The second order will successfully create, even with the same movies and customer. And with that, we've dynamically changed the validation policy at run time.
Notes about the Simple Policy pattern
Like the Property Container implementation, our implementation of the Simple Policy pattern is very simple, offering only a glimpse of what the pattern can do. Policies are very powerful when associated with containment hierarchies.
Take the example of a chain of movie rental stores owned by a parent company. The parent company sets a default sales policy for all branches, but each branch is also empowered to implement its own sales policy. Thus, a branch could set an individual policy that would be contained by the larger company policy at run time. The algorithm would then be set to check the store for a default movie sales policy; if one did not exist the parent company would be queried for the default policy, otherwise the store's individual policy would be used.
In this section we implemented two design patterns from SanFrancisco Design Patterns. These patterns were defined for a specific type of application, in this case business applications.
Integrating the Property Container into our application allows us to add
attributes, such as release date, to our Movie class at run time,
making the overall movie catalog easier to update. The Simple Policy
implementation isolates validation logic, letting us change our sales policy at
will. We'll use the code from both of these examples in the next section, as we
continue building our Web-based movie-ordering application.
See Resources to learn more about these two design patterns and others to be found in SanFrancisco Design Patterns.



