The introduction of catalogs of patterns has been a great advancement to software engineering. These collections of common solutions to common design problems are an excellent way to pass knowledge of the software engineering craft from one practitioner to another. I am happy to have several great texts from this category taking up valuable shelf space. Design Patterns, Patterns of Enterprise Application Architecture, the Pattern-Oriented Software Architecture series, Release It!, and Enterprise Integration Patterns are some of my favorite references that continue to be a source of inspiration whenever I encounter difficult design decisions. However, they are not the last word in software engineering that some engineers present them as. Acting as though pointing out that a pattern exists is a valid appeal to authority for using that pattern is like saying that serving seafood to those that are allergic is acceptable because the seafood was prepared using the recipe of a master chef.
Obviously, there will be production problems if you serve seafood at your dinner party when some or all of your guests are allergic. Seafood despite how well it is prepared is simply not the appropriate solution to the dinner problem in this case. The same is true of design patterns. While all of the design patterns I have encountered in the catalogs I’m familiar with have been great solutions to the problems they address there is still a need to be aware of what problems you are trying to fix, and what the pattern addresses. Using an inappropriate design pattern is bad design because you have to accept the consequences of that design decision without getting the benefits.
Here is an example I’ve encountered too many times to count using the Singleton pattern from GoF. In this example we have an object that is “expensive” to create, and that we only need one of at any given time. The basic argument is that since the object is expensive to create, and we only ever need one instance it should implement the Singleton pattern. Let’s look first at the applicability of Singleton from GoF:
Use the Singleton pattern when
- there must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
- when the sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.
So, grasshopper why is this a bad use of the Singleton design pattern?
First, a Singleton is for use when “there must be exactly one instance” this doesn’t fit with our “expensive to create” object where we don’t really care if there is more than one instance, because if it’s not a requirement that breaks the functionality of the system it is very likely we will want more than one object in the future.
Second, the typical Singleton implementation statically links classes to the procedure, method, function, or what ever that creates and grants access to the Singleton. This static linkage makes unit testing classes that depend on the Singleton very difficult if the Singleton contains some state or depends on some dynamic stateful part of the system.
Third, I would argue that the Singleton pattern itself as stated in GoF is bad design. This is based on the second part of the first statement for applicability “it must be accessible to clients from a well-known access point.” This implies a client is looking up the Singleton from a global well-known access point. This introduces additional coupling to the Singleton implementation. Not only do I need to know the interface to the Singleton, but I need to now how to locate it. I also know that it is a Singleton, which is knowledge of the specific implementation. Locally a class should not care that some object must be a Singleton, yet this pattern insures that it must. I would think that all dependencies with lifetimes exceeding that of the dependent object regardless of being a Singleton or not should be introduced by injection, because it eliminates the coupling to the creation and/or lookup mechanism.
Fourth, you have no way of knowing this from the high-level description above but many of the encounters that fall into this example actually turned out to be not really that expensive to create after all. It is actually often times the case that it is cheaper to create a new object and allow it to die when the referencing scope dies than it is to maintain some kind of reference to that object, and the facilities to look it up etc. I have found this to be the case often times in Java. There are still many engineers out there making bad design decisions based on outdated performance data from the initial releases of the Java platform, this despite the fairly well known issues with premature optimization taking precedence over good design.
While it is nice to know about design patterns, more important is the basic “science” of software engineering. Is the class your creating cohesive? Is there too much coupling between two or more classes? Is the functionality properly encapsulated? Is the functionality repeated in many places? Would there be a benefit to adding a layer of abstraction? Does this design decision require the difficult enforcement of global policies or does it work locally? These are not always easy questions to answer, but once you have your answers you can state the design problem and just maybe find a ready made expert solution to that problem. However, the best chefs don’t measure, or follow a specific recipe to the letter because they are the authority. They just know what feels right, and do that. I’ve noticed the best engineers do the same thing.