When we are learning how to code, we usually only worry about making software work - and that’s already pretty important. However, as we make progress towards our journey as software developers, we start considering other aspects that can improve the quality of the things we develop. For instance, if we are working on a project with other people, how can we make the code more readable, easy to understand, to maintain, and to evolve? How can we avoid code duplication? How can we make the code more flexible?
We often hear some concepts related to trying to solve these issues, such as the famous Design Patterns. However, what exactly are Design Patterns? We can start talking about the things they are not.
Design Patterns are not magic formulas that solve problems. They are not code snippets that can be inserted into our application. They are also not algorithms. Design Patterns, as the name suggests, are just patterns that offer a concept of a solution for a design problem that happens frequently. Implementation of each pattern can change according to the domain, language, and other factors. These patterns were created from the experience of several developers who were trying out different solutions for recurring problems they faced. A book called Design Patterns: Elements of Reusable Object-Oriented Software was published a long time ago and presented 23 Design Patterns. Since then, other patterns were identified. These patterns have even come to influence programming languages and, as everything in software engineering, they are frequently changing.
Why should we learn Design Patterns?
Imagine you’re doing something for the first time. For instance, learning a new recipe. You know where you want to get and you have an ideia of how to mix the ingredients to do so. However, it would be very helpful if you could follow a recipe that would guide you to achieve this result in a way that several people have done before and avoiding common problems in the way. That’s sort of how it works with design patterns.
A lot of developers have tested these patterns, faced these problems, and documented these solutions. Besides that, Design Patterns define a common language for us to talk about possible solutions with our teams, for instance, while pairing.
Is it the same as SOLID principles?
No, but it has everything to do with it. Actually, I believe it’s necessary to have a better understanding of SOLID principles before starting to study Design Patterns, because these principles are used in most of the patterns. The goal is to help us create software that is easy to maintain and to understand. The name SOLID is an acronym for:
- Single Responsibility: a class should have only one responsibility.
- Open/Closed: classes should be closed for change and open for extension. In other words, if a class works properly and has been tested, we should avoid changing it. To insert new behaviour, we should extend the class instead of modifying it.
- Liskov Substitution: a subclass should be able to substitute the superclass without changing the behaviour of the system.
- Interface Segregation: a class shouldn’t depend on methods it doesn’t use.
- Dependency Inversion: dependencies can determine how easy it is to make changes to the system. If we have a lot of them, that can lead to high coupling. This principle says that high level modules should depend on high level generalisations, and not on details.
Design Patterns follow some of these design principles to cover topics such as flexibility and maintainability. For instance, a lot of Design Patterns use the Open/Closed system, following the idea that it should be possible to extend your software, or the Dependency Inversion patterns, abstracting behaviour through interfaces. That’s why it makes sense to first have a good understanding of design principles and then continue to Design Patterns.
What type of Design Patterns exist?
Design Patterns were classified in three different types, based on its purpose.
Creational Patterns are about the process of creating objects. Depending on the context or on our goal, the traditional way of creating objets might not be the best solution. For example, if we have a class that connects to a database, we may want to create just one instance of this class, instead of having new connections in different places of the system. For that we can use a pattern called Singleton. There are other patterns included in Creational Patterns, such as Factory Method, Builder, Prototype, and others, all of them trying to solve problems related to the creation of objects.
Structural Patterns entails how objects are connected and their relationships. These patterns are necessary because the more a system grows, the more complex it gets. With more complexity, we need to find a sustainable way of maintaining the relationship between objects in which we allow for the creation of new functionality in a simple manner. For instance, let’s say we created a hamburger and we want to add items dynamically, such as extra cheese or a salad. Instead of creating a hamburger class and several subclasses with each type of additional item, we could apply the Decorator patterns, which uses aggregation to dynamically combine behaviours. Other well known patterns in this category include Adapter, Composite, and Facade.
Behavioural Patterns deal with how objects distribute work, how they collaborate to achieve a common goal. If we go back to the hamburger case and we want to let our customers know every time a hamburger is ready for delivery, we can use a pattern from this category, which is called Observer. We would have clients observing and waiting to be notified. This type of pattern include others well known, such as Command, Iterator, and Strategy.
How can we know which patterns to use?
There is a huge catalog of Design Patterns and it’s very unlikely we will remember all of them after reading a book about the subject. The important thing is to first know that these patterns exist. After that, we need to understand what problem we are trying to solve. Then, we can deepen our knowledge in the patterns that try to solve these problems. Having a better understanding of how the pattern is applied and looking at example code is a good starting point. It’s important to remember there is no “right answer” when it comes to Design Patterns. It is a choice that should be made based on the context and its needs, and evaluating the trade-offs.
References and Study Materials
- Book: Design Patterns: Elements of Reusable Object-Oriented Software