Something that I was thinking about a lot when I first started working as a developer was why do we use all those complicated design patterns. A senior colleague looked at a piece of code I had written and told me that it would be better if I use a Singleton.
I didn’t fully understand why I had to use someone else’s complicated pattern instead of writing something simple on my own.
Thankfully my opinion changed but I decided to write this in case others are wandering the same thing. But before we go into the technicalities, let’s look at an everyday example.
You probably follow certain principles in your life - a set of general rules. Maybe you don’t drink coffee after 5pm or avoid sugary drinks. Maybe you don’t do risky investments or you don’t want to live on the first floor.
We do that so when we’re in a situation we can make quick decisions based on them instead of considering the problem from the ground up. When someone asks you if you’d like coffee after dinner you know the answer. When a friend is telling you about her new risky business venture you know that you won’t be investing.
We accept our principles as guiding rules when facing a challenge. Design patterns are similar to that but for software architecture. They are principles to follow so we won’t have to approach each situation like it was new. They are widely accepted and formalized solutions to common problems. A design pattern solves a specific programming challenge related to structure, usability or maintainability.
When engineers are faced with the same challenges over and over again, effective solutions will be created. Those solutions will then be named and given guidelines on how to use them properly. This solidifies it as a pattern that is proven to work well.
Think about caching for a bit. It solves the problem of repetitive computing operations and frequently accessed data. If we have an answer ready, we needn’t do the work to calculate it again.
A design pattern is like accessing a cached architectural solution for your problem. When an established pattern can solve the problem we’re facing it makes sense to apply it instead of developing something on our own.
You can think of design patterns as effective mental models for common engineering problems. The most widely used design patterns are separated in three groups - creational, structural and behavioral.
When to Use a Design Pattern?
They’re not meant to be applied to the letter. A design pattern isn’t a finished design that you copy and paste. Think of them as a guideline, a blueprint about how you should be approaching a specific challenge.
We should recognize when a design pattern is a good solution but we shouldn’t be actively leaning towards them in all cases. Deciding when to use a pattern is more art than science.
When you’re working on something, first try to get a simple solution together. Then review it, think how it can be extended and improved. Is there an underlying general problem that you think yo ucan apply a design pattern to?
Knowing that the design patterns exist certainly helps but you shouldn’t be aiming to remember their implementation by heart. There are a lot of them and some are specific to a particular technology - you can’t possibly learn all of them.
Consider if others have encountered this problem before you. Maybe a more tested and proven paradigm has been created b y the community already
Imagine that you’re building a class that exports some data to a JSON file. If you implement just the class with an
export method on it you won’t be wrong. But if you think about it, in a month you may have to make another class that exports to CSV, for example.
Many developers have faced that problem before and there’s a behavioral pattern for it called Strategy. You can create a common interface that defines the export method and then you can have separate classes that implement that interface. You’re now assured that you will have a consistent implementation of the export functionality.
Another example is when you’re adding a 3rd party library to your codebase. Some of them have bad APIs - verbose, hard to reason about without reading the documentation. You don’t want to work directly with it so you wrap it in your own class. That way you deal with the bad API only once and as a bonus it’s easier to change it in the future.
That’s another design pattern called an Adapter. You don’t have to follow the textbook examples, you can use them as inspiration to approach a problem.
One last example - consider the MVC (Model-View-Controller) pattern. It’s a commonly used way to structure back-end applications. You can never be wrong if you pick it as a general solution.
But this doesn’t mean that every web application should be built on top of an MVC architecture. A small project could be deployed as a set of simple lambda functions - no need of serious structure. A REST API could be organised around the ideas of domain driven design.
The great benefit of using established patterns is that they’re easily recognized. They act as a common language in your team. When you say that something should be implemented as a Singleton, for example, they will all know what you mean.
It will be easier to navigate a project when it has a familiar structure or naming conventions. If you have a Rails background and open any other MVC project you will find your way around it.
The principles are the same even if the technology is different.
- Design patterns are like principles, general rules that have proven to work well
- When faced with frequently occurring problem we can follow those rules and patterns instead of solving the problem from scratch
- Design patterns are established, understood and easily recognized software practices
- We should be aiming to recognize when an existing pattern is applicable to our problem. But we shouldn’t be forcing one when it’s not the obvious solution.