Useful Design Patterns
Design Patterns have a role of an eye-catching picture to me and whenever I want to write a piece of code I’ll stay in front of this picture and while I enjoy of this beautifulness, I think how can I transfer this beauty to my code.
What will we learn from design patterns? You know you don’t want to reinvent the wheel, so you look to Design Patterns
Creational design patterns:
creational design patterns try to give ready-to-use objects to users instead of asking for their creation.
- Singleton: as the name implies, it will provide you with a single instance of an object, and guarantee that there are no duplicates.
In Go, there’s nothing like static members which we have in pure oop languages, but we have package scope to deliver a similar result.
- Builder: helps us construct complex objects without directly instantiating their struct, or writing the logic they require.
- Factory: Its purpose is to abstract the user from the knowledge of the struct he needs to achieve for a specific purpose, such as retrieving some value, maybe from a web service or a database.
Imagine that you want to organize your holidays using a trip agency. You don’t deal with hotels and traveling and you just tell the agency the destination you are interested in so that they provide you with everything you need. The trip agency represents a Factory of trips.
- Prototype: The aim of the Prototype pattern is to have an object or a set of objects that is already created at compilation time, but which you can clone as many times as you want at runtime.
The main objective for the Prototype design pattern is to avoid repetitive object creation.
- Abstract factory: is a new layer of grouping to achieve a bigger (and more complex) composite object, which is used through its interfaces.
Structural design patterns:
As the name implies, help us to shape our applications with commonly used structures and relationships.
- Composite: The Go language, by nature, encourages use of composition almost exclusively by its lack of inheritance. in this pattern you will create hierarchies and trees of objects.
- Adapter: allow us to use something that wasn’t built for a specific task at the beginning, The Adapter pattern is very useful when, for example, an interface gets outdated and it’s not possible to replace it easily or fast. Instead, you create a new interface to deal with the current needs of your application, which, under the hood, uses implementations of the old interface.
- Bridge: tries to decouple things as usual with design patterns. It decouples abstraction (an object) from its implementation (the thing that the object does).
- Proxy:usually wraps an object to hide some of its characteristics. These characteristics could be the fact that it is a remote object (remote proxy), a very heavy object such as a very big image or the dump of a terabyte database (virtual proxy), or a restricted access object (protection proxy).
- Decorator: the big brother of the Proxy pattern, it allows you to decorate an already existing type with more functional features without actually touching it.
When you need to add functionality to some code that you don’t have access to, or you don’t want to modify to avoid a negative effect on the code, and follow the open/close principle.
When you want the functionality of an object to be created or altered dynamically, and the number of features is unknown and could grow fast.
Just keep in mind that the Decorator is commonly used when you want to add functionality to an object at runtime, like in our web server. It’s a compromise between what you need and what you want to sacrifice to achieve it.
- Facade: in architectural terms, is the front wall that hides the rooms and corridors of a building. It protects its inhabitants from cold and rain, and provides them privacy. It orders and divides the dwellings.
It shields the code from unwanted access, orders some calls, and hides the complexity scope from the user.
A library is a form of facade
So, you use the Facade design pattern in the following scenarios:
1- When you want to decrease the complexity of some parts of our code. You hide that complexity behind the facade by providing a more easy-to-use method.
2- When you want to group actions that are cross-related in a single place.
3-When you want to build a library so that others can use your products without worrying about how it all works.
- Flyweight: is a pattern which allows sharing the state of a heavy object between many instances of some type.
Imagine that you have to create and store too many objects of some heavy type that are fundamentally equal. You’ll run out of memory pretty quickly. This problem can be easily solved with the Flyweight pattern, with additional help of the Factory pattern.
What’s the difference between Singleton and Flyweight then?
With the Singleton pattern, we ensure that the same type is created only once. Also, the Singleton pattern is a Creational pattern. With Flyweight, which is a Structural pattern, we aren’t worried about how the objects are created, but about how to structure a type to contain heavy information in a light way.
Behavioral design patterns:
- Strategy: uses different algorithms to achieve some specific functionality. These algorithms are hidden behind an interface and, of course, they must be interchangeable. All algorithms achieve the same functionality in a different way. For example, we could have a Sort interface and few sorting algorithms. The result is the same, some list is sorted, but we could have used quick sort, merge sort, and so on.
- Chain of resposibility:The single responsibility principle implies that a type, function, method, or any similar abstraction must have one single responsibility only and it must do it quite well. This way, we can apply many functions that achieve one specific thing each to some struct, slice, map, and so on.
- Command: we focus on the invocation of something or on the abstraction of some type.
An example for the organic world would be a box for a delivery company. We can put anything on it but, as a delivery company, we are interested in managing the box instead of its contents directly.
- Template: we will try to achieve something similar but with just part of the algorithm.
reusability by abstracting the parts of the code that are not common between executions
- Memento: The Memento design pattern usually has three players (usually called actors):
1-Memento: A type that stores the type we want to save. Usually, we won’t store the business type directly and we provide an extra layer of abstraction through this type.
2-Originator: A type that is in charge of creating mementos and storing the current active state. We said that the Memento type wraps states of the business type and we use originator as the creator of mementos.
3-Care Taker: A type that stores the list of mementos that can have the logic to store them in a database or to not store more than a specified number of them.
- Interpreter: it can be useful to design a small language for a variety of reasons: repetitive tasks, higher-level languages for non-developers, or Interface Definition Languages (IDL) such as Protocol buffers or Apache Thrift.
- Visitor: we are trying to separate the logic needed to work with a specific object outside of the object itself. So we could have many different visitors that do some things to specific types Visitor and Visitable.
- State:A light switch is a common example of an FSM. It has two states on and off. One state can transit to the other and vice versa. The way that the State pattern works is similar.
- Mediator: will act as the type in charge of exchanging communication between two objects. This way, the communicating objects don’t need to know each other and can change more freely. The pattern that maintains which objects give what information is the Mediator.
- Observer: is especially useful to achieve many actions that are triggered on one event.
Concurrency design patterns:
- Barrier: is a very common pattern, especially when we have to wait for more than one response from different Goroutines before letting the program continue.
Imagine the situation where we have a microservices application where one service needs to compose its response by merging the responses of another three microservices.
The Barrier pattern opens the door of microservices programming with its composable nature. It could be considered a Structural pattern, as you can imagine.
The Barrier pattern is not only useful to make network requests; we could also use it to split some task into multiple Goroutines. For example, an expensive operation could be split into a few smaller operations distributed in different Goroutines to maximize parallelism and achieve better performance.
- Future/promise: itallows us to write an algorithm that will be executed eventually in time (or not) by the same Goroutine or a different one.
we will define each possible behavior of an action before executing them in different Goroutines.
- Pipeline: is a powerful pattern to build complex synchronous flows of Goroutines that are connected with each other according to some logic.
If this then that, or else something else. Pipelines pattern can be made more complex by using a few functions that call to each other. They can even get looped in their out execution.
Head First Design Patterns: Building Extensible and Maintainable Object-Oriented Software
Shared via Kindle. Description: What will you learn from this book? You know you don't want to reinvent the wheel, so…