So, using design patterns is probably something you already do; you just didn't realize it or hadn't put a name to it. Putting names to things and then cataloging and classifying them is important because it allows us to share and communicate with others at a higher level of abstraction. Although there have been several good books written about design patterns, including Patterns for e-business: A Strategy for Reuse by IBM Press, the quintessential reference is Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides or, as it is now commonly referred to, "The Gang of Four Book." "The Gang of Four Book" assigns names and categories to the most common design patterns.
Pattern Categories
So let's take a look at some of the general categories that design patterns fit into and then dig a little deeper into some examples. First, most design patterns can be generalized into a Creational, Behavioral, or Structural pattern. Creational patterns are patterns that help you instantiate objects. The goal of most Creational patterns is to allow you to create objects through at least one level of indirection, which allows you run-time flexibility. Behavioral patterns are concerned with how objects collaborate with each other at run-time to achieve a common goal. The key concept of Behavioral patterns is that a group of coordinated objects can accomplish tasks that a single object could not accomplish on its own. Structural patterns describe methods of class inheritance and object composition.
Creational Patterns
Although there are a number of Creational patterns, an easy one to start with is the Singleton. For most classes, we want to be able to instantiate multiple objects. However, from time to time we have need of a class from which we can instantiate one and only one object. A Singleton has two objectives. First, it must allow for the creation of exactly one object of the type it is responsible for. Second, it must allow for easy access to the object. The following is a scaled down example of a class that I use when I want to establish a database connection.
{
private static SingletonConnection _instance = null;
private static Connection connection = null;
private SingletonConnection()
{
// put your code to connect to the database
// and set the Connection object
// this.connection = your code here
}
public static SingletonConnectiongetInstance()
{
if (_instance == null )
{
_instance = new SingletonConnection();
}
return _instance;
}
public Connection getConnection()
{
return this.connection;
}
}
Don't be worried if this class looks very strange to you. The first thing you probably noticed is the private, class-level variable _instance, which is of the same type as the class itself. This allows you to store a single instance of the class in the class itself, but because it is private, it can be accessed only through the public, class-level method getInstance(). The method getInstance() returns the private object if one exists or creates one if needed. The other unusual thing you probably noticed is that the constructor is private. Most programmers are not aware that this is legal syntax. Making the constructor private forces users of the class to use the getInstance() method when they want a database connection. So to add a database connection to any class, we simply add the following line of code.
Now, we have fulfilled both of our goals by ensuring that only one object is created and that it is easy to access.
Behavioral Patterns
Of the three pattern categories, I tend to use Behavioral patterns the most. One of the reasons for this is that Java has built-in language constructs for my two favorites, the Observer pattern and the Iterator pattern. These are both patterns that you have probably already discovered on your own.
The Observer pattern is often referred to by one of its aliases, such as Publish/Subscribe or Model/View. The problem the Observer pattern solves is common in, but not limited to, GUI application. Suppose we have some data that is stored in one object and we have several other objects that display this data to an end user in a variety of formats, such as raw numbers, a bar graph, and a line graph. We call the object that contains the data the Observable object and those that are interested in displaying the data the Observers. Java implements the Observer pattern using the classes java.util.Observable and java.util.Observer. We simply have our classes inherit from these base classes and we get all the functionality we need. The Observer classes register with the Observable class by calling its addObserver(Observer o) method. When something happens to its data that it thinks the Observers would be interested in, the Observable class calls its own notifyObservers() method, which in turn calls the update() method of each of the registered Observers. Each of the registered Observers should override the update() method inherited from their parent to take the appropriate action when the Observable object calls notifyObservers(). The call to notifyObservers() is usually tied to some sort of state change in the Observable object data.
Another Behavioral pattern that I use a lot and that is directly supported in Java is the Iterator pattern. Java is loaded with various types of aggregate objects, most of which implement the java.util.Collection interface. One of the methods that an aggregate class implementing java.util.Collection must implement is the iterator() method, which returns a lightweight class that represents all the objects contained within the Collection. The two key methods of Iterator are hasNext() and next(). The method hasNext() tells you if there is another object left in the Iterator, and the next() method returns the next object in the Iterator. The following code assumes that you have instantiated an object called myCollection that implements the java.util.Collection interface.
while (myIterator.nasNext())
{
Object myObject = myIterator.next();
// Put code here to do something with each object in the collection
}
The advantage of using the Iterator pattern is that it decouples how the collection is stored from how it is accessed. In other words, we can change out the object myCollection for any object that implements the Collection interface without changing our code snippet.
Structural Patterns
There are a number of useful Structural design patterns. The most common are the Composite pattern, the Facade pattern, and the Adaptor pattern. The Composite pattern is often used in drawing programs or in user interfaces. The basic idea is that objects are organized into hierarchies, but each node of the hierarchy can itself be a hierarchy and each node can be treated the same way, whether it is a single component or a hierarchy of components. The Facade pattern provides a gateway interface that can be used to wrap a collection of objects inside a single interface. Facades are an excellent way to partition a complex or highly coupled system into subsystems. The Adaptor pattern is used to transform the interface of one class into the interface of another. Adaptors are effective when you have classes that are part of third-party libraries, but their interfaces do not match up well with your system. You simply add an Adaptor class between your system and the third-party class.
Using Patterns
As you have already seen, design patterns use objects and classes as their building blocks. In some cases, one design pattern can be used as a component of another. For example, the list of registered Observers in the Observer pattern is stored in a class that implements a Collection interface for which an Iterator pattern is used to call the update() method on each registered Observer. Although the "Gang of Four Book" list is a good start, there are many more design patterns. In fact, new design patterns are being discovered and published all the time. You will also find that you can create your own patterns based on slight variations in known patterns. For example, imagine if you needed a pool of objects that were instantiated ahead of time and could be returned to the pool when you were done with them. You could modify the Singleton pattern to have a notion of a finite number of instances as well as a method for returning an object when you were finished with it.
Design patterns can also be combined together into larger, systemwide patterns that are often called frameworks. As you begin incorporating design patterns into your systems, you will notice that where you used to design one object at a time, you will now think at a higher level and begin to design things as collections of patterns. A few words of advice: Although you will quickly master several patterns, don't be satisfied until you understand at least the small collection of patterns presented in the "Gang of Four Book." Otherwise, you run the risk of the proverbial carpenter whose only tool is a hammer and therefore views all problems as nails.
Conclusion
Patterns are a way to leverage proven designs. Although you have probably discovered a number of design patterns on your own, having a catalog of patterns with standard names helps us to better communicate about them to each other. A catalog also enables us to combine and derive new patterns more easily. Remember, to use patterns effectively, you should be familiar with as many as possible so you can be sure that you are applying the most appropriate one to a given situation.
Now, the next time you get that feeling of "I've done this before," you will have an extra set of tools at your disposal.
Michael J. Floyd is an Extreme Programmer and the Software Engineering Technical Lead for DivXNetworks. He is also a consultant for San Diego State University and can be reached at
LATEST COMMENTS
MC Press Online