Read time: 9 minutes
Design Patterns can be intimidating at first. It’s one of those topics that tends to attract mystery and misunderstanding around itself, making it look like you can only really understand it after you pass through some initiation rite. They have also generated heated debates in software development circles. Some people swear by them, while others argue eloquently to their uselessness and even harmful nature.
This post is aimed at people who are new to the topic and represents an attempt at dispelling some of the dark clouds hovering around it. Where do Design Patterns come from? What are they, really, and what kinds of problems do they solve? What are their benefits and drawbacks? These are the questions we’re hopeful to find some answers to in the following paragraphs.
Origins
The idea of Design Patterns was introduced by Christopher Alexander, an architect, in his 1977 publication, A Pattern Language: Towns, Buildings, Construction.
Alexander noticed there were some design constructs that, when applied to classes of recurring problems, tended to yield the desired results. As such, he decided to document these patterns so that his experience could benefit others and even empower people to ‘design their own’.
In Alexander's own words:
“The elements of this language are entities called patterns. Each pattern describes a problem that occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.”
A Pattern Language: Towns, Buildings, Construction
One of the most widely-cited examples of such a pattern is ‘a place to wait’. Bus stops and hospital waiting rooms would both qualify as instances of the general design problem that this pattern applies to. The striking high-level differences between these two instances of the problem speak to the generality of the pattern.
Adoption in Software Design
It was Kent Beck, along with Ward Cunningham who started experimenting with the idea of applying patterns to software design, around 1987. Several years later, in 1994, the book Design Patterns: Elements of Reusable Object-Oriented Software (aka Gang of Four, aka GoF) was published, which played a crucial role in the popularization and adoption of the concept in the industry.
In the same book we can find the following, rather detailed, definition of a design pattern in OO:
“A design pattern systematically names, motivates, and explains a general design that addresses a recurring design problem in object-oriented systems. It describes the problem, the solution, when to apply the solution, and its consequences. It also gives implementation hints and examples. The solution is a general arrangement of objects and classes that solve the problem. The solution is customized and implemented to solve the problem in a particular context.” (emphasis mine)
If the above definition still sounds a bit abstract, let’s briefly consider the Decorator Pattern as an example. A Decorator “allows behavior to be added to an individual object, either statically (i.e., at compile time) or dynamically (i.e., at run-time), without affecting the behavior of other objects from the same class”1. It achieves this by subclassing the original object while also holding a reference to it. Then, it can intercept all calls intended for the original object and alter the behavior as desired. This arrangement allows for “multiple Decorators to be stacked on top of each other, each time adding new functionality to the overridden method(s)”1.
Motivation
It's important to stress, even at the price of being repetitive, that design patterns don't solve programming problems in the strict sense, but software design ones. In other words, they are not algorithms, but arrangements of objects and classes that elegantly balance the conflicting forces that give rise to design problems.
So why should we care about software design?
Because, as Heraclitus noted a long time ago: “Everything changes and nothing stands still”.
His saying applies to software at least as much as it does to anything else. Software requirements change often and, if a system isn’t well-designed, keeping it up to date will be a pain.
The above image is a reasonable illustration of the problem. It would be much easier to track a cable in the second half of the image, than it would be in the first. The second setup also makes for a nice top-level view, while the first looks like a perfect mess.
Granted, when there are just a few cables, the effort required to keep them well-organized might not seem worthwhile. As the system grows, however, the benefits far outweigh the effort. In fact, the design effort might well be the decider of the system's long-term survival.
Similarly, it is much harder to understand what a system does, when it is made up of tightly-coupled, all-depending-on-each-other objects with huge methods and no real separation of concerns. The code is harder to navigate and touching any part of it will trigger an avalanche of problems in completely unexpected areas. Investing in things like modularity and separation of concerns will significantly reduce the maintenance burden and provide for code reuse opportunities.
It's not to say that this should, or necessarily could, all be done upfront. Rather, it's about taking the time to perform the necessary refactoring steps as new requirements occur, stressing the current design beyond its intended limits.
Patterns versus Principles
With all this talk of Software Design, one might be wondering why hasn't there been any mention of Design Principles so far. KISS, DRY, Separation of Concerns, Modularity, Least Surprise or even the SOLID principles of OO design – how do these fit in?
Or, to slightly rephrase that: how do Design Patterns relate to Design Principles?
Principles are more general and higher-level than Patterns. They hold true across the whole paradigm. They are not limited to a context. Patterns could be viewed as condensations, or concrete applications, of Principles – recipes for solving context-bound design problems.
The following analogy from a StackOverflow thread illustrates the difference pretty well:
“Principle: We should teach others in order to educate ourselves as well as others, and overall make our nation a progressive nation
Pattern: In our country, each doctor graduate is supposed to teach 6 months in a far-away village to complete his/her degree”
With that said, it follows that a good understanding of basic Design Principles should facilitate a smoother transition to Design Patterns.
Classification
The classical Design Patterns are usually divided into three groups, as follows:
Creational
These Patterns deal with object creation mechanisms. Their aim is “to separate a system from how its objects are created, composed and represented. They increase the system’s flexibility in terms of the what, who, how and when of object creation”2.
A few common examples:
- Singleton – ensures that a type has only one instance and controls the global access to it
- Builder – separates the construction of a complex object from its representation, thus allowing the same construction process to create different representations
- Factory method – allows a class to defer instantiation to subclasses
Structural
Structural Patterns deal with identifying simple ways of realizing relationships between objects.
Common examples:
- Adapter – adapts the interface of an existing class to be used from another interface, thus allowing for otherwise incompatible classes to work together
- Bridge – decouples an abstraction from its implementation so that the two can vary independently
- Decorator – allows extending the behavior of certain objects without affecting other objects that belong to the same class
- Proxy – acts as an interface to another object (e.g. a network connection, a file etc.)
Behavioral
Behavioral Patterns identify and realize common communication patterns between objects.
Common examples:
- Command – encapsulates an action and its parameters
- Iterator – provides sequential access to elements of an aggregate object without exposing its underlying representation
- Mediator – encapsulates how a set of objects interact
- Strategy – allows for different algorithms belonging to the same family to be selected at runtime
Providing a concrete implementation of a Pattern is beyond the scope of this introductory article. One can, however, refer to the Decorator pattern post for such an example.
All Design Patterns are well-documented in such a way that both the problem and the solution in terms of relationships between classes and objects are easy to understand. To give you an idea, the documentation template described in GoF follows the structure outlined in the table below (extracted from here).
Term |
Description |
Pattern Name |
Describes the essence of the pattern in a short, but expressive, name |
Intent |
Describes what the pattern does |
Also Known As |
List any synonyms for the pattern |
Motivation |
Provides an example of a problem and how the pattern solves that problem |
Applicability |
Lists the situations where the pattern is applicable |
Structure |
Set of diagrams of the classes and objects that depict the pattern |
Participants |
Describes the classes and objects that participate in the design pattern and their responsibilities |
Collaborations |
Describes how the participants collaborate to carry out their responsibilities |
Consequences |
Describes the forces that exist with the pattern and the benefits, trade-offs, and the variable that is isolated by the pattern |
Pros
There are two major benefits to Design Patterns that are hardly disputable.
The first is they offer time-tested, proven solutions to recurring design problems. These help isolating the areas of the system that are prone to frequent change by promoting modularity and loose coupling. The end result is systems that are easier to understand, maintain and extend.
The second benefit is the common language that patterns provide. It is much easier to refer to a design solution by a standard pattern name, e.g. Decorator, than by ad-hoc descriptions that are more subject to misunderstanding, e.g. 'a wrapper around a base type', 'a type that is both in a <hasa> and an <isa> relationship with the base type' etc.
The following phrase is a perfectly reasonable example of patterns parlance: 'We will use Decorators for our Streams and work around the constructor chaining problem by employing Builders'. Someone with a good understanding of the Decorator and Builder Patterns will know what is meant by that right away.
Cons
On the down side, Patterns tend to introduce additional classes, objects or even entire new layers to the designs they touch. That can equate to extra complexity and in some cases, lower performance.
It can also be relatively easy, especially as a beginner, to misunderstand and misuse these highly abstract constructs. Guidance from a more experienced developer can prove highly valuable in this case.
Conclusion
Design Patterns can be viewed as condensed pieces of wisdom resulting from craftsmen’s day-to-day struggle with recurring problems. They are a means by which new practitioners encountering the same design problems can tune in to their forerunners’ wisdom and hopefully avoid some of their pains.
Despite their criticism, GoF Design Patterns are being used heavily in various production-ready libraries and frameworks, especially in the realm of statically typed languages (e.g. C++, Java, C#). They make for an interesting and rewarding study. Their value can best be appreciated after having first struggled with the problems they solve.
Further Reading
Below is a list of useful resources for people who are looking to find out more about Patterns: