As software developers, we should always try to find out the best way to structure our code. There are many problems that, through time, have become common to almost every developer. This is where the Design Patterns come in.
Design Patterns were created as methods to solve common problems. So we decided to understand a little more about this topic, did some research, and applied it to a real-life situation.
Through this article, we will talk about some of the most used Design Patterns and how they can be applied in a developer’s everyday life.
The design pattern concept first appeared in 1977 in a book called “A Pattern Language: Towns, Buildings, Construction” which was written by Christopher Alexander. Back in those days, it was written thinking about construction and urban design following the patterns of the most beautiful buildings/constructions in the world. Years later, in 1994, a group of computer scientists known as the Gang of Four took the idea and applied it to software development. This was documented in a book called “Design Patterns: Elements of Reusable Object-Oriented Software”.
This book describes a total of 23 Design Patterns and most of them are still applicable today for most developers in a bunch of different situations. So, the main idea is: “Design Patterns are elegant solutions to repeating problems in software design”. The patterns are divided into 3 main groups:
– Creational – the creational Design Patterns are about, as the name suggests, creating objects. It provides mechanisms that increase flexibility and reuse of code.
– Behavioral – the behavioral Design Patterns are about the communication/interaction between objects, making it more efficient.
– Structural – the structural Design Patterns are about relationships between objects. They explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.
For our study, we picked 4 of the most used Design Patterns: singleton, builder, factory method, and observer. We will explain in some detail how these patterns work.
This is a creational pattern responsible for creating an object and ensures that only one instance of it is created. It also provides a global point of access to this instance.
A common problem where this can be applied is, for example, a company that wants to add a new printing feature to its applications. This requires a single instance of the object which is the printer and also a global point of access to multiple applications.
Code-wise, the implementation of a library to a printing system is fairly simple. The singleton class usually has 3 main members:
– A static member: this contains the instance of the singleton class;
– Private constructor: this prevents anybody else from instantiating the class;
– Static public method: this provides the global access point to the object.
The next image shows a possible implementation of a printer solution with a singleton in C#.
The Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.
Imagine that you have two types of objects: a Customer and a Store. The customer is very interested in a particular brand of product (say, it’s a new iPhone model) which is soon expected to become available in the store.
The customer could visit the store every day and check product availability, but while the product is still en route, most of these trips would be pointless.
On the other hand, the store could send tons of emails (which might be considered spam) to all customers each time a new product becomes available. This would save some customers from endless trips to the store. Though, simultaneously, it’d upset other customers who aren’t interested in new products.
The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the changes to its state, we call it a publisher. All other objects that want to track changes to the publisher’s state are called subscribers. The Observer Pattern suggests that you add a subscription mechanism to the publisher class so individual objects can subscribe to or unsubscribe from a stream of events coming from that publisher.
So we make buying a Tesla an example. Imagine we have some clients that are interested in buying a Tesla that has a head price of 100 000,00€ but they are not so eager to give that much money, so they would like to be notified if and when the price drops out. We can use the Observer pattern to solve this problem.
We created 2 interfaces to define the methods that our classes should implement: ISubject and IObserver.
We created a class Tesla that implements the ISubject class. This is going to be the publisher mentioned above. It has a list of observers, names, and prices as properties. So every time the price is updated we notify our observers.
We then created a TeslaClient class that has the name and the wantedPrice and implements the Update method from IObserver that currently writes in the console that the Tesla’s price is below/equal to the wantedPrice.
Below is our main method: we initialize with a client list interested in the Tesla and we create an observer/publisher that will hold its names and the prices. We attached our subscribers to the observer and finally, we updated the price of the Model X. John Doe should only be the client notified because the 85000 < 90000.
And the result is:
A builder pattern should only be used for creating complex objects, like when object creation needs to be separated from its assemblies, such as trees or houses. It allows you to produce different types and representations of an object using the same construction code. Imagine a complex object that requires laborious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried inside a monstrous constructor with lots of parameters, or even worse: scattered all over the client’s code. The Builder pattern suggests that you extract the object construction code out of its own class and move it to separate objects called builders.
The next image shows a possible way to implement the builder pattern.
The Factory Method is a design pattern that defines an interface for creating an object but lets the classes that implement the interface decide which class to instantiate. Factory pattern lets a class postpone instantiation to sub-classes. The factory pattern is used to replace class constructors, abstracting the process of object generation so that the type of the object instantiated can be determined at run-time.
Now, imagine that you are creating a logistics management application. The first version of your App can only handle transportation by trucks, so the bulk of your code lives inside the Truck class. After a while, your App becomes pretty popular. Each day, you receive dozens of requests from water transportation companies to incorporate water mail logistics into the App. This will require making changes to the entire codebase. Moreover, if later you decide to add another type of transportation to the App, you will probably need to make all of these changes again.
The Factory Method pattern suggests that you replace direct object construction calls (using the new operator) with calls to a special factory method. Don’t worry: the objects are still created via the new operator, but it’s being called from within the factory method. Objects returned by a factory method are often referred to as products.
First, we declare the types of Vehicles: Truck and Ship.
Then we create an interface that contains the methods that the Vehicles should implement:
Right after, it is necessary to implement the interface on our Vehicles classes:
Next, we create an abstract class that contains an abstract method to be overridden in the classes that extend this abstract class:
Here we extend the TransportFactory class and override the method and we return the correct class by the type of the vehicle argument:
Here is where we instantiate our transportation methods:
And the result is this:
As we have seen through this article, there are significant advantages to using Design Patterns in development. It can largely improve the written code, make it easier to understand, and can easily solve problems that otherwise would be harder to solve. We should also take into consideration that every time we use Design Patterns we are making it easier for other developers to pick up our code and debug it, or add new code on top of it.
Of course, Design Patterns are not something that developers can just copy and paste on their code but the general idea of each one of them can be adapted to fulfill the need of a large set of situations. The patterns are tried and tested solutions and, therefore, are reliable options to solve all kinds of problems using the object-oriented paradigm. It also makes it easier to communicate with your teammates since it’s a common “language” between developers. You can just say something like “Use a Singleton for that task” and everyone will know what you’re talking about.
For all this and more, we can safely assume that the use of Design Patterns at companies, on a professional level, will greatly benefit the development of their products.