Similar to my other "5 Minutes of Less" repositories, the rationale is to demonstrate and explain, with minimal means, things that many of us developers seem to think are hard. By driving down the difficulty and concretely showing examples, I hope to make these concepts easier to understand and learn more about.
Clone the repo.
You can install the dependencies with npm install
and then just go at each file with npx ts-node src/{type}/{filename}.ts
.
Or, if you're lazy, you can also copy the individual TS file's contents into the TypeScript playground if you want to avoid cloning and installing anything at all.
In short, patterns are ways of organizing code that are identified and (often) catalogued. Typically, patterns, once named become known in the software community. Many patterns have existed for a very long time, and patterns are less fragile to the course of time than implementations and languages, which change year by year, decade by decade.
No one can work outside of patterns — ultimately all code has some pattern(s) in it.
Design Patterns are part of the software engineering canon, and are useful bits of practical knowledge that help in "blocking out" solution spaces for problems. Applying them haphazardly, clumsily, and without thinking about the specifics of our problem space will not lead to good use of these.
In programming, few problems are truly novel. That's an important part of understanding design patterns, whether they are (actual) architectural design patterns (like those of Christopher Alexander) or software design patterns, such as these.
Competent and thoughtful use of these patterns in our work helps organize and solve our problems in ways that can solve any number of similar problems, at least in theory, and if we have matched problem and pattern adequately.
With this said, design patterns are always constructs that must be applied. Patterns won't directly solve your programming woes.
If you're like me, and if you read Design Patterns: Elements of Reusable Object-Oriented Software, you may agree that it's a bit of a tough book to swallow.
Some of the obvious hard parts of it include:
- The book lists 23 different patterns. While they all follow a common, sound convention it's more of reference book rather than something you read in one go.
- The book is ~30 years old, and the examples are therefore using C++ and SmallTalk. I know that I am not a C++ guy, and SmallTalk is pretty dead by now.
- Also, the diagrams weren't even UML (as that didn't exist at the time), and these diagrams are generally, for many, somewhat obtuse to follow.
I'm not here to dismiss the book — quite the contrary! It's a classic and especially the sections on each pattern's applicability and consequences are great and well worth reading.
Clearly, the patterns have survived to this day. But it's time to look at, learn, and fiddle with the examples in a language that suits you. For me, that's TypeScript/JavaScript.
I also believe that with concrete examples, senior developers will already be able to intuit many trade-offs of each pattern.
Please see the referenced resources below when you are ready to dive deeper!
All patterns are organized the same way as in the book Design Patterns. All patterns include a description, and start with its intent quoted from the book.
The flow is: Creational patterns
> Structural patterns
> Behavioral patterns
.
Consider what should be variable in your design. This approach is the opposite of focusing on the causes of redesign. Instead of considering what might force a change to a design, consider what you want to be able to change without redesign. The focus here is on encapsulating the concept that varies, a theme of many design patterns. [The table below] lists the design aspect(s) that design patterns let you vary independently, thereby letting you change them without redesign. — "Design Patterns: Elements of Reusable Object-Oriented Software", page 29
Purpose | Design Pattern | Aspect(s) That Can Vary |
---|---|---|
Creational | Abstract Factory | families of product objects |
Builder | how a composite object gets created | |
Factory Method | subclass of object that is instantiated | |
Prototype | class of object that is instantiated | |
Singleton | the sole instance of a class | |
Structural | Adapter | interface to an object |
Bridge | implementation of an object | |
Composite | structure and composition of an object | |
Decorator | responsibilities of an object without subclassing | |
Facade | interface to a subsystem | |
Flyweight | storage costs of objects | |
Proxy | how an object is accessed; its location | |
Behavioral | Chain of Responsibility | object that can fulfill a request |
Command | when and how a request is fulfilled | |
Interpreter | grammar and interpretation of a language | |
Iterator | how an aggregate's elements are accessed, traversed | |
Mediator | how and which objects interact with each other | |
Memento | what private information is stored outside an object, and when | |
Observer | number of objects that depend on another object; how the dependent objects stay up to date | |
State | states of an object | |
Strategy | an algorithm | |
Template Method | steps of an algorithm | |
Visitor | operations that can be applied to object(s) without changing their class(es) |
This useful diagram is in the Design Patterns book. I've redrawn it here, as it's helpful in organizing the relations of patterns.
The authors have their own set of recommendations:
If you aren't an experienced object-oriented designer, then start with the simplest and most common patterns:
- Abstract Factory
- Adapter
- Composite
- Factory Method
- Observer
- Strategy
- Template Method
- Decorator
My own top 5 recommendations (A-Z order), in terms of usefulness and relative ease, would be:
For transparency, I've used and adapted ChatGPT 3.5 examples of these patterns. Explanations are mine, and the adaptation of code is done to an extent where I feel it looks like "my code" and what I would write and show.
Everything is cross-checked with the book to validate correctness.