As software developers, we should always try to find the best way to structure our code. There are many problems that have become familiar to almost every developer over time. This is where Design Patterns come into play.
Design Patterns were developed as methods to solve common problems. So we decided to learn a bit more about this topic, did some research, and applied it to a real-life situation.
In this article, we will talk about some of the most commonly used Design Patterns and how they can be applied in a developer’s daily life.
The concept of design patterns first appeared in 1977 in a book entitled “A Pattern Language: Towns, Buildings, Construction” written by Christopher Alexander. At the time, it was about construction and urban design based on the patterns of the world’s most beautiful buildings/constructions. Years later, in 1994, a group of computer scientists known as the Gang of Four took this idea and applied it to software development. This was documented in a book titled “Design Patterns: Elements of Reusable Object-Oriented Software”. In this book, a total of 23 design patterns are described, and most of them are still applicable to most developers today in a number of different situations.
So the main idea is: “Design Patterns are elegant solutions to repetitive software design problems”.
The patterns are divided into 3 main groups:
For our study, we selected 4 of the most commonly used design patterns: Singleton, Builder, Factory Method, and Observer. We will explain in detail how these patterns work.
This is a creational pattern that is responsible for creating an object and also ensures that only one instance of it is created. It also provides a global access point to that instance. For example, a common problem where this can be applied is an organization that wants to add a new print feature to their applications. This requires a single instance of the object, namely the printer, and a global access point for multiple applications.
Code-wise, implementing a library into a printing system is quite simple. The singleton class usually has 3 main members:
The next image shows a possible implementation of a printer solution with a singleton in C#.
Observer is a behavioral design pattern that allows you to define a subscription mechanism to notify multiple objects of any events that happen to the object they’re observing.
Imagine you’ve two types of objects: a customer and a store. The customer is very interested in a particular branded product (e.g., a new iPhone model) that will be available in the store soon. The customer could visit the store every day to check the availability of the product, but as long as the product is still on the way, most of these visits would be pointless.
On the other hand, the store could send tons of emails (which could be considered spam) to all customers every time a new product is available. This would save some customers endless trips to the store. But at the same time, other customers who’re not interested in new products would be annoyed.
The object that has a state of interest is often called a subject, but since it will also notify other objects of changes in 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 proposes to add a subscription mechanism to the Publisher class so that individual objects can subscribe or unsubscribe to a stream of events coming from that Publisher.
Let us take the purchase of a Tesla as an example. Imagine we have some customers who are interested in buying a Tesla that has a top price of €100 000.00, but they are not so eager to spend that much money, so they want to be notified when the price drops. 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 have created a class Tesla that implements the class ISubject. This will be the Publisher mentioned above. It has a list of observers and name and price as properties. So every time the price is updated, we notify our observers.
We then created a TeslaClient class that has the name and wantedPrice and implements the Update method of IObserver, which currently writes to the console that the Tesla’s price is below/equal to the wantedPrice.
Below is our main method that we initialize with a list of customers interested in the Tesla, and we created an observer/publisher that includes its names and prices. We connected our subscribers to the observer and finally we updated the price of the Model X. John Doe should only be the customer that is notified because the 85000 < 90000.
And the result is:
A builder pattern should be used only for creating complex objects, for example, when the creation of an object must be separated from its assembly, as with trees or houses. It allows you to create different types and representations of an object with the same construction code. Imagine a complex object that requires tedious, step-by-step initialization of many fields and nested objects. Such initialization code is usually buried in a monstrous constructor with many parameters, or worse, scattered throughout the client’s code.
The Builder pattern proposes to separate the object construction code from its own class and move it into 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 leaves it up to the classes that implement the interface to decide which class to instantiate. Factory pattern allows a class to defer instantiation to subclasses. The factory pattern is used to replace class constructors and abstract the object creation process so that the type of the instantiated object can be determined at runtime.
Now imagine you are creating an application for logistics management. The first version of your application can only handle shipments involving trucks, so most of your code is in the Truck class. After some time, your app becomes quite popular. Every day you receive dozens of requests from water transport companies to integrate water mail logistics into the app. This requires changes to the entire code base. And if you later decide to add another type of transport to the app, you will probably have to make all these changes again.
The factory method pattern suggests that you replace the direct calls to object creation (using the new operator) with calls to a special factory method. Do not worry: objects will still be created with the new operator, but it will be called from the factory method. Objects returned from a factory method are often called 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 in this article, using design patterns in development provides significant benefits. They can greatly improve written code, make it easier to understand and solve problems that would otherwise be more difficult to solve. We should also keep in mind that every time we use design patterns, we make it easier for other developers to take our code and debug it or add new code to it. Of course, design patterns are not something developers can simply copy and paste into their code, but the general idea of each of them can be adapted to meet the needs of a wide variety of situations.
The patterns are tried and tested solutions and therefore reliable options for solving all kinds of problems using the object-oriented paradigm. They also facilitate communication with your teammates, as it is a common “language” for developers. You can simply say, “Use a singleton for this task,” and everyone will know what you are talking about.
For all these reasons, we can assume that the use of Design Patterns in companies at a professional level will greatly benefit the development of their products.