What are design patterns?
If I haven’t harped on this point enough, I want you to keep thinking to yourself “Design Patterns are systems” over and over. Let’s say that again, design patterns are systems, and systems are designed to solve specific problems. It doesn’t matter if it’s the brake system in your car, the biological systems that run our bodies, or the banking system in Figure 1.2. They’re all systems and they’re all trying to solve a problem or keep their respective system organized and balanced (and sometimes both).
Figure 1.2: Diagram of how bank accounts work using credit and debit
More than anything, design patterns focus on making code more flexible and reusable, two tenets we’ll hammer away at throughout this book. Just keep repeating our mantra every time we dive into a new pattern: design patterns are systems!
The Gang of Four
Back in 1994, a group of four daring engineers banded together to better tackle OOP practices and recurring problems that kept popping up in their programs; Design Patterns: Elements of Reusable Object-Oriented Software written by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides is the product of that team-up. The book covers 23 design patterns split into three categories according to their core functions, with examples written in C++ and Smalltalk. While the book was and continues to be an important resource, these patterns have been adapted and expanded since 1994, leaving some more (and some less) relevant depending on your chosen programming language and environment.
There are also those who have pointed out that a fair few of the patterns are replacements for perceived missing features in the C++ language. However, this argument has never resonated with me when I look at my code in other languages (whether it’s C# or something like Swift) because wearing my systems-thinking glasses has always made me think more critically and write code more intentionally.
You’ve likely heard the phrase “Gang of Four” whispered with quiet reverence (or loud and angry fist-shaking, depending on who you’re talking to), but with topics like this, I’ve found it’s best to learn design patterns as a skill before making judgments. There are impassioned programmers on both sides of the aisle, which means getting your hands dirty is the best thing you can do for yourself. A lot of the debate gets lost in theoretical or pedagogical minutiae (and personalities) but the core skillset behind design patterns has always been a useful tool for programming.
Pattern categories
There are three categories that all original design patterns fall into – Creational, Behavioral, and Structural. As with all things, additional patterns have evolved since 1994, resulting in useful patterns that were not included in the original Gang of Four book that we’ll cover on our journey and a few honorable mentions (because we can’t cover every design pattern in a single book).
Before we get into the category details, I feel it’s important to address a problem that crops up at the beginning of most journeys into design patterns – how do I find the right pattern for the problem I’m facing? Do I need to read and memorize every pattern, or is there a better way to navigate this topic?
The answer might surprise you, but no, this isn’t a memorization game and you don’t get extra points for knowing everything about every design pattern – systems thinking is a learned skill, not a closed-book test. It’s almost a detective game: first, knowing what problems each pattern category addresses is super important because it narrows the field you have to search. Second, reading the first few pages of each chapter in the applicable category will show you pretty quickly if you’re in the right place. From there, the more you use design patterns, the more you’ll get a feel for the problems and effective solutions out in the wild. As you’ll see, design patterns offer solutions to well-documented problems, but they’re not set in stone; it’s up to you to adapt them to your project.
Now that we know the basics, let’s dive into the nitty-gritty of each design pattern category and what specific problems they aim to solve.
Creational patterns
Creational patterns deal with creating objects that are uniquely suited to a given situation or use case. More specifically, these patterns deal with how to hide object and class creation logic, so the calling instance doesn’t get bogged down with the details. As your object and class creation needs become more complex, these patterns will help move you away from hardcoding fixed behaviors toward writing smaller behavior sets that you can use to build up more complex features (think LEGO). A good creational pattern black-boxes the creation logic and simply hands back a utility tool to control what, who, how, and when an object or class is created.
The creational patterns we’ll cover are listed in the following table:
Pattern |
Description |
Singleton |
Ensure a class has only one instance and provide a global point of access to it – commonly used for features like logging or database connections that need to be coordinated and shared through the entire application. |
Prototype |
Specify the kinds of objects to create using a prototypical instance and create new objects from the “skeleton” of an existing object. |
Factory Method |
Define an interface for creating a single object, but delegate the instantiation logic to subclasses that decide which class to instantiate. |
Abstract Factory |
Define an interface for creating families of related or dependent objects, but let subclasses decide which class to instantiate. |
Builder |
Allows complex objects to be built step by step, separating an object’s construction from its representation – commonly used when creating different versions of an object. |
Object Pool |
Avoid expensive acquisition and release of resources by recycling objects that are no longer in use – commonly used when resources are expensive, plentiful, or both. |
Table 1.1: List of creational design patterns with descriptions
Behavioral patterns
Behavioral patterns are concerned with how classes and objects communicate with each other. More specifically, these patterns concentrate on the different responsibilities and connections objects have with each other when they’re working together. Like structural patterns, behavioral patterns use inheritance to divvy up behaviors between classes, which gives you the freedom to let go of any white-knuckled control flow responsibilities and focus on how objects can work together.
The behavioral patterns we’ll cover are listed in the following table:
Pattern |
Description |
Command |
Encapsulate a request as an object, thereby allowing for the parameterization of clients with different requests and the queuing or logging of requests. |
Observer |
Define a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically. |
State |
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class – commonly used when object behavior drastically changes depending on its internal state. |
Visitor |
Define a new class operation without changing the underlying object. |
Strategy |
Define a family of interchangeable behaviors and defer setting the behavior until runtime. |
Type Object |
Allow the flexible creation of new “classes” from a single class, each instance of which will represent a different type of object. |
Memento |
Capture and externalize the internal state of an object so it can be restored or reverted to this state later – without breaking encapsulation. |
Table 1.2: List of behavioral design patterns with descriptions
Structural patterns
Structural patterns focus on composition, or how classes and objects are composed into larger, more complex structures. Structural patterns are heavy on abstraction, which makes object relationships easier to manage and customize. Patterns in this category will use inheritance to let you mix and match your class structures as well as create objects with new functionality at runtime.
The structural patterns we’ll cover are listed in the following table:
Pattern |
Description |
Decorator |
Attach additional responsibilities to an object dynamically keeping the same interface. |
Adapter |
Convert the interface of a class into another interface clients expect. An adapter lets classes work together that could not otherwise because of incompatible interfaces. |
Façade |
Provide a unified interface to a set of interfaces in a subsystem. Facade defines a high-level interface that makes the subsystem easier to use. |
Flyweight |
Shares common data between similar objects to limit memory usage and increase performance. |
Service Locator |
Provide a global access point for services without coupling client code to the concrete service classes. |
Table 1.3: List of structural design patterns with descriptions
The goal with each of these problems is to use a solution that is coherent, reusable, and above all, designed for efficient change.