You probably follow certain principles in your life. 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 all have certain general rules that we follow. 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.
You can’t know the implementation of all design patterns by heart. But you can be aware of their existence and the problems they solve. That way you can build up intuition on when to use them or recognize them when you find them in the wild.
Let’s consider two more examples in the software engineering world - memoization and caching. They solve problems with repetitive computing operations and frequently accessed data. If we have an answer ready, we needn’t to the work to get to it again.
A design pattern is like accessing a complete 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.
The same approach is used in architecture and mechanics. When faced with similar problems, software engineers often reach similar solutions. At the time GraphQL was being created at Facebook, the engineers at the Washington Post were developing a similar technology. Design patterns don’t get established by an entity. They get formalized because many programmers have come to the same successful solution.
When do you apply a design pattern?
We should recognize when a design pattern is a good solution but we shouldn’t be actively leaning towards them in all cases.
Design patterns are not meant to be applied to the letter, they are general solutions. Some of them are widely applicable no matter the business domain. The Observer design pattern could find an application in each application that needs to notify different components of an event. Others are more specific and they apply to a tighter scope of problems.
How do you know when to use one, though? An established pattern should be used only when it’s obvious to do so. We shouldn’t try to fit a problem to a solution. If it’s too specific, forcing it to a stock solution won’t produce a good result. The patterns are principles and it should be clear when they can be applied.
An example is the MVC (Model-View-Controller) pattern that is commonly used to build web applications. It’s a good general solution and in most cases you won’t be wrong if you use it. It makes code better organized and it’s quite popular so most engineers understand it well.
But this doesn’t mean that every web application should be built on top of an MVC architecture. A small application could be deployed as a set of simple lambda functions - no need of serious structure. A REST API could be structured around the domain entities that it works on. An application built on top of DynamoDB would probably use a repository instead of models due to the way the database works.
The great benefit of using established patterns is that they’re easily recognized. When you open a project and you see familiar naming and structure you will find your way much faster. Even if you don’t know the domain well, being aware of the principles will help you navigate the code.
Let’s use the MVC example again. Imagine that you have some experience with a framework such as Rails. If you have to work on a Laravel project you won’t have much trouble even though it’s written in another language. You know that controllers handle http requests and that models represent the domain entities. If you need to do some work in the UI you will look for the views. The principles are the same even if the technology is different.
When you are discussing architecture with your colleague you can address the design pattern by its name. Most engineers would understand you or at least know what to look up. There’s plenty of information about them and its widely accessible.
Design patterns are good and proven solutions. And then there are anti-patterns - the exact opposite. They are frequently occurring but are undesirable and considered bad programming practices because they don’t produce desirable effects.
An anti-pattern is a practice that leads to more problems instead of solutions. Much like their positive counterpart - they start forming as principles. A solution to use whenever you face a certain problem. But with time the solution has proven to be troublesome. When many developers using the same software design principles see problems with it, it starts classifying as an anti-pattern.
Take the God Object anti-pattern for example. This is a class that knows too much or does too many things. Instead of separating the functionality in many small classes or functions, we have put it in a single “all-knowing” object. It maintains most of the data that the app needs and provides the methods for its manipulation.
This is a pattern that has proven to cause more harm than good. Instead of having multiple components communicating to each other, such programs are tightly coupled to the God Object. A single change to it can have unintended consequences across the whole application.
The reasons to label a technique as an anti-pattern vary. It may make code hard to maintain, read, scale or extend. The bottom line is that they have more negative than desirable effects. So, while we shouldn’t be forcing design patterns, we should be active avoiding anti-patterns.
When we find that we’re using a technique widely considered harmful we should reevaluate our decisions and consider different approaches.
- 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.
- Anti-patterns are the opposite - they are frequently occurring practices that have proven to cause more harm than good. We should be actively avoiding them.