Design Patterns: A Retrospective
Design patterns are like the instruction manuals that come with your dining table, you’ll never need them unless you want to enjoy your meals on a flat surface. Based on that statement, you can tell that I’m not entirely impartial when it comes to software design patterns. I’ve been on both ends of the spectrum, from indifference to the type of fondness you only have for comfort food.
If you’re like me you probably implemented design patterns without really understanding them. I only fully appreciated the concept after (an admitted struggle) reading the GoF Design Patterns book. Legend has it that, after writing copious amounts of code, the “Gang of Four” realized that they were solving recurring problems. For the good of the lazy, they published Design Patterns: Elements of Reusable Object-Oriented Software (aka GoF Design Patterns), a book that catalogs 23 solutions to common programming problems.
The Design Patterns concept was adapted from Christopher Alexanders’ A Pattern Language. In the book, Alexander et al. describe reusable solutions to architectural problems. Architectural as in buildings and doors and all that stuff. The Gang of Four applied the same concept to software engineering, specifically object-oriented software design. They used practical solutions implemented in C++ and Smalltalk, both popular languages at the time (the early 1990s). Initially the term “Design Patterns” almost exclusively referred to the patterns described in this book. Over the years more patterns have been developed but the core definition remains the same: A design pattern is a repeatable solution to a commonly occurring problem.
Design patterns are not simple, they were never meant to be because the problems they solve are not simple. As with all non-simple things, there’s a danger of misinterpretation which leads to misuse, abuse, and bad code reviews. I went through a ‘design pattern frenzy’ where I tried to implement patterns in everything until everything was an untidy ball of regrets. There are no shortcuts with design patterns. They come with complexity and consequences which have to be taken into consideration.
Some design patterns encourage bad practice. I’m looking at you, Singleton. Quoting Kent Beck in his book Test-Driven Development By Example, he says, “How do you provide global variables in languages without global variables? Don’t. Your programs will thank you for taking the time to think about design instead.” Singletons are considered harmful because they create globals and globals are bad. There are cases where Singletons are useful, for instance, application logs. It’s much easier to have one instance of your log class which you can use throughout your application without worrying about initialization. It’s considered acceptable because logging shouldn’t affect the execution of your application.
There are harsher criticisms that question the existence of design patterns. In 1996, Peter Norvig made a presentation titled Design Patterns in Dynamic Languages where he demonstrated that most design patterns become simpler or even unnecessary in Lisp. Paul Graham, in his blog post Revenge of the Nerds, says: “I wonder if these patterns are not sometimes evidence of case ( c ), the human compiler, at work. When I see patterns in my programs, I consider it a sign of trouble.” These two arguments lead to a broader question, are design patterns missing language features?
Despite the criticism, Design Patterns have made an impact. User interface designers have a cataloged list of 100+ design patterns, documented very similarly to the GoF design patterns. React developers have a free ebook that addresses react in patterns. There are countless collections of design pattern implementations in multiple languages (Python, Golang, Java).
Back when I started learning Golang, I wanted to see how tests were written in production. That’s how I began contributing to ncw/rclone. The first contribution I ever made loosely revolved around the Abstract Factory Pattern.
Rclone is a command line utility that syncs files and directories to and from different cloud storage providers. In this PR I implemented a way of emptying the trash on Google Drive. For some context on the code above, Fs is an interface that represents a file system and must be implemented by each cloud storage object. To implement an interface in Go, a struct has to implement all the interface methods. Cloud storage providers don’t have similar functionality so @ncw wrote a Features() method that returns all the optional features in a file system. CleanUp is one of those features. I didn’t have to write the empty trash method in the Google Drive struct, it’s already available in the official drive package. All I had to do was implement the CleanUp feature in the Google Drive struct, calling the empty trash method from within. The PR had 28 lines added, most of which was documentation. It took me less than 2 hours to put the whole thing together.
That’s the power behind Design Patterns. They have a steep learning curve but the benefits are worth it, especially with large engineering teams working on expansive codebases. Better coordinated engineers means less technical debt, more productivity and better cycles.
Design Patterns are a subjective choice in an objective discipline. If all else fails, KISS.