Design Patterns: Elements of Reusable Object-Oriented Software
April 7th, 2024
What I Won't Talk About
I'll begin saying that my intention isn't to cover each design pattern present in Design Patterns, along with their implementations. That's what the book does. If you want to learn each design pattern in detail, read the book. Instead, I'll be focussing on the general ideas that span the entire book. I'm going to share what key design principles I learned, what I found interesting, and how well I think this book holds up after nearly 30 years since its publication.
A Time Before Java
The most significant obstacle Design Patterns needs to overcome is staying relevant to a modern reader. Most implementation code in this book is written in C++, with a smaller amount written in Smalltalk. Smalltalk, although not part of the current popular programming languages zeitgeist, is still used in industry. C++, of course, remains relevant to this day.
But why doesn't this book use Java, the most popular object-oriented programming language of all time? Design Patterns was published in 1994, or 30 years ago at the time I'm writing this. Those who are more atune than I with the history of programming languages would know that Java was released in 1995. Therefore there is no Java code in the book. If there was, I could have added that to my list of "evidence to support the existence of time travellers", which remains empty.
Although the date this book was publish is mostly unconsequential to the overall goal is succeeds achieving, there is one area where its age shows. Many of the case studies examine softwares are largely irrelevant nowadays. If you can tell me what Motif, Presentation Manager, or Unidraw are, you're probably old enough to remember the collapse of the Soviet Union. Of course the authors had no way of knowing these softwares would fall out of fashion. Though, because the softwares are unfamiliar, it is sometimes difficult to understand what problems in the software certain design patterns solve.
Why Design Patterns are Important
Design patterns are software abstractions one level above classes and objects. The book argues that "composition at the pattern level rather than the class or object levels lets us achieve the same synergy with greater ease" (349). Viewing a program as a system of interconnected design patterns rather than a system of interconnected classes and objects saves time and effort by removing the need to "...solve every problem from first principles" (1). One wouldn't start designing an object-oriented program by implementing their own definition of classes and objects, so why should one do the same with design patterns? The fundamental reason for design patterns is to avoid the work required to rediscover this higher abstraction level.
In fact, just being aware of design patterns helps one reason about software systems more like an expert. What separates an expert from a novice is the ability to design a software system that is flexible and maintainable while minimizing the manpower, time, and cost needed to create that system. The authors of Design Patterns say that the reason they chose the word design patterns is because "...they make programs more resistant to re-design and refactoring" (353). Being able to create an effective system design from these design patterns is what allows for easy code refactoring, and saves time, money, and effort in the long run.
The Two Principles of Object Oriented Design
Design Patterns outlines two principles of object-oriented design, those being: (1) program to an interface, not an implementation, and (2) favor object composition over class inheritance. Following these two principles closely generally results in the separatation of the complexity of a behavior from its class or object.
Program To An Interface, Not An Implementation
The majority of inheritance in design patterns is strictly single inheritance. In fact, it is very rare to find a subclass of a subclass in this catalog. The reason for this is that anything more than single inheritance often results in a class relationship that is too dependent on its compile-time structure to make a software system sufficiently flexible. The main use case for single inheritance is having one or more concrete classes inherit from an abstract class so that they share a common interface. As a result, all classes or objects which interact with the design pattern are easily decoupled from the class heirarchy.
Favor Object Composition Over Class Inheritance
Since anything more than single inheritance is actively discouraged, Design Patterns suggests that object composition should be used wherever possible over subclassing. Like single inheritance, object composition helps decouple software systems. The major difference between these two approaches is that object composition allows object interactions to be dynamic at run-time, where class relationships are fixed at compile-time.
Object composition is not perfect though, since its main advantage also comes at a cost. While single inheritence prevents individual classes from growing too complex and becoming "monolithic", object composition can inadvertantly lead to the opposite occuring. For example, the Mediator* pattern's internal cohesion is inversely proportional to the number of colleague objects it interacts with. This book studies how to decrease coupling between objects in great detail, but often glosses over methods to prevent low cohesion within an object. Of course there is discussion about single responsibility, and how to maintain high cohesion during implementation, but this information is not extensive enough in my opinion.
This book warns the reader to keep the balance between complexity and indirection in mind when adding design patterns to a software system. Design patterns achieve reduced system complexity primarily through code indirection. Although indirection can makes code more reusable, it can also make a program difficult to understand. Therefore if a design pattern's use does not create more flexibility, maintainability, or reusablility in a software system, its addition is counterproductive.
*Any common noun capitalized as proper noun means it is a pattern from the Design Patterns catalog.Things to Think About
After reading this book I'm more skilled in spotting design patterns in programs I interact with. For example, I now recognize that the React library relies on the Composite pattern to structure components, and uses the Template pattern for its hooks. To anyone reading this article in 2054, I apologize for using such an a outdated framework as an example.
Similarly, there were many times when concepts that I'm familiar with were presented in a new, object-oriented way. Before reading this book, I was familiar with interpreters and how they operate on abstract syntax trees in the context of functional programming. But in the object-oriented world, an Interpreter pattern operates on an abstract syntax tree which, in the context of object-oriented programming, is a Composite pattern. I find it really neat how design patterns can intuitively recreate the structure and behavior of familiar ideas within the limits of the object-oriented design space.
The authors say that "...the theme of many design patterns..." is "...encapsulating the concept that varies..." (29). The table on page 30 lists what design aspect varies for each design pattern. In my opinion, this table holds the most concise description of each design pattern. I would argue that this tables describes some patterns better than their intent (summary) sections in catalog. As a novice, referencing this table from time to time helped me gain a better understanding of what problem each design pattern is intended to solve. This provides merit to the idea that the variant behavior of a design pattern is fundamental to its identity and use cases.
While this book provides a catalog of object-oriented design patterns, the authors acknowledge that it is not a comprehensive list. Design Elements prompts the reader to "look for patterns you use, and... make them part of your documentation" (358). They say that this book is also an invitation to "explore the [larger] space of design patterns" (356), not just the one exclusive to object-oriented systems. Rather than providing a fixed guide for implementing reusable software, their primary goal is to equip the reader with the know-how to recognize what design choices lead to more reusable software.
Table of Contents