I've had a tumultuous relationship with design patterns over the last 10 years. Our relationship started out pretty rocky as I tried to navigate the murky waters of the infamous Gang of Four (GoF) book, Design Patterns, Elements of Reusable Object-Oriented Systems. With some luck and the help of a mentor early-on though, I was able to avoid some common patterns problems before they caused a serious rift between design patterns and me. In this post, I'm going to share what works for me. I hope this advice will help you avoid some of the difficulties I've encountered with design patterns over time.

An Approach to Learning

Here is the rough approach I’ve taken in learning design patterns. To this day, I try many of these ideas when I encounter new patterns.

Seek Patterns in Core APIs

When I started with design patterns, the GoF book was the only book available for learning about them. The style of the GoF book is elegant, but a difficult read. If you don’t approach the material as an active reader, it can be difficult to consume. In order to help the patterns set in better, I searched for as many real world examples that I could find. Fortunately for me, the core Java API was chockfull of patterns. Once I learned to seek them out, they were pretty easy to spot. One easily spotted example is the Decorator pattern in java.io’s Input and Output Streams APIs. Users of java.io are able to seamlessly add capabilities to their stream instances by simply wrapping the streams with another stream implementing the functionality they need such as buffering, compression, object serialization and many others. The beauty of the Decorator pattern in this case is that you are able to pass any instance of the stream in the hierarchy to methods that require Input and Output streams. The receiver doesn’t need to know any of the details.

Another interesting example of design patterns is Java’s Remote Method Invocation (RMI) API’s use of the Proxy design pattern. Users of RMI enabled applications can simply program to an agreed upon interface and the RMI subsystem takes care of all the gritty details of marshaling and unmarshaling objects and invoking methods remotely. Users of an RMI enabled service are actually working with a class instance that implements their shared interface, yet it shields them from all of the complexity.

Don’t Be Afraid to Experiment

I often experiment with design patterns as I develop non-critical portions of systems. I like to try out the patterns without the risk of breaking something important. For instance, I was able to thoroughly learn how to apply the Prototype pattern for instance using this method. Our team was conducing a proof of concept on a card management system. Our card agreements shared a lot of details, but still needed to be customizable for each customer. By cloning an instance of a standard agreement, we were able to create a object hierarchy that allowed for customizing any number of the agreement’s details.

Ask Your Friendly Pattern Zealot

Some times I’m not always sure if I need to use a pattern to a solve design problem. In these cases, it helps to reach out to experts. I’ve had good success so far reaching out to other experts. It saves me the pain of misapplying a pattern or making a design more complicated than it needs to be. If you’re not comfortable asking knowledgeable co-workers directly, it doesn’t hurt to include them in your architecture reviews. Your co-workers will be able to see the big picture of the design and provide useful feedback to you. Regardless of how you include an expert in your design process, doing so will help improve the design quality.

What I’ve Learned So Far

I’ve already learned a number of important lessons while working with patterns:

  • Wait until you repeat yourself.
  • Use common names for pattern collaborators
  • You can always apply a pattern later

Wait Until You Repeat Yourself

A number of times I found myself applying patterns to problems that didn’t need them. Instead of designing an elegant subsystem, I ended up with something resembling more of a Rube Goldberg Machine. The main problem in these cases was that I didn’t need design patterns in the first place. For instance, a few times I used the State pattern to model simple state systems. When your objects need to alter their behavior and have many states, the state pattern is often a useful choice. In a simple state system however (say 2 or 3 states at most), it’s usually easier to maintain a couple of if-else blocks then full-fledged context and state instances. For instance, if you were writing a Button GUI component, it would likely be overkill to use the State pattern for each of the button’s different states.

Use Common Names for Pattern Collaborators

Though you can choose any name for your collaborators when you apply patterns, you should do your best to choose the collaborator names used in the pattern description. Sticking with consistent names allows other developers (and even future you) to get a good understanding of how the system fits together without needing to trace through all of the source.

For example, suppose you were designing an authentication subsystem that needs to authenticate users against a database and an LDAP store. In addition, you will need to support a proprietary authentication system in the future. The Strategy pattern is a great choice in this scenario. Each strategy might implement a simple interface such as this:

  public interface AuthenticationStrategy {
  	boolean authenticate(String user, String password);
  }

In this scenario, you might use class names such as DatabaseAuthenticationStrategy and LdapAuthenticationStrategy for your concrete strategies. These names are useful as they describe the class intent without the viewer needing to open the class source file.

You Can Always Apply a Pattern Later

Sometimes you see a problem that you know could be simplified by some pattern, but you just can’t find a pattern to help you out. If you can’t find a pattern now, it’s often better to wait until a good candidate emerges later. Often, I’ve found that when it’s hard to apply a pattern to a design problem, I don’t really need one in the first place. The benefit of waiting is that you’re not forcing yourself to shoehorn a solution into your design. You’ll be less likely to pick the wrong pattern this way. Unit tests and powerful refactoring tools present in many modern IDEs make it fairly easy to modify existing designs into patterns later.

What Next?

Hopefully you’ll find my strategies above useful. Now that you have a game-plan in place, you should make sure you have a good book handy to aid your quest for pattern nirvana. The GoF book is a true technology masterpiece, but it’s probably not the best place to start if you’re just starting out with patterns. Instead you should consider finding a popular patterns book that talks about applying design patterns for your particular platform of choice. These books are great because they show you how to handle the various peculiarities of your platform. In many cases, these platform specific books are often easier than mapping examples written in Smalltalk to your platform of choice (assuming that Smalltalk isn’t your platform of choice of course!). Once you’ve mastered the core design patterns, the GoF provides a concise and solid reference that I’ve yet to find in any other book.

comments powered by Disqus