There are various common problems that we might face while trying to design reusable, extensible and maintainable software. These problems are generally related to object creation, defining class structure, assignment of responsibilities between objects, enabling objects interaction without tightly coupling, etc.
To solve the recurring problems in software design, various patterns have been identified, tried, and tested over the years. Design Patterns are reusable solutions to recurring problems in software design. They help create software that are extensible, less complex, easy to understand, maintain, and, refactor.
Design Patterns are independent of programming languages and represent templates or descriptions for solving a particular design problem. Each design pattern solves a particular design problem. By studying the design patterns we can leverage the wisdom and lessons learned by experts over the years.
Types of Design Patterns
Creational Design Patterns
Creational Design Patterns deal with the way classes are instantiated and objects are created. These design patterns are used when there is a specific requirements related to creating objects.
Let us look at a design problem related to object creation.
Example
Let's say that we have a class named Dog that implements the Animal interface. We can create an object of the Dog class like this:
Dog dog = new Dog();
Let's say that there are multiple classes that implement the Animal class.
If the use case requires having our code run on different Animal objects irrespective of the concrete class, the above code might create issues. Here, we are creating the dog object by instantiating the Dog class directly.
We should instead be abstracting the object creation process. Here, we can use the Factory Method Design Pattern which is one of the creational design patterns.
Factory Method:
Animal initializeAnimal(String type) {
if(type.equals("Dog")) {
return new Dog();
} else if(type.equals("Cat")) {
return new Cat();
} else if(type.equals("Monkey")) {
return new Monkey();
}
throw new Error("Invalid Animal Type");
}
Initialization:
Animal animal = initializeAnimal(animalType);
There can be other design problems related to object creation as well based on our design requirements. We may want to use one of the creational design patterns based on the use case.
We will learn about the Creational Design Patterns in further articles.
Structural Design Patterns
Structural Design Patterns deal with how objects and classes can be composed or assembled together to form a larger structure or subsystem that can offer some additional specific functionality. They help assemble the classes and make sure that the structure is extensible and reusable.
Let us look at a design problem related to structuring classes.
Example
Let’s say there is a class JSONGraphPlotter with a function plot(data) that can plot beautiful graphs based on JSON data.
Let's say that we have a use case of creating graphs based on XML data. We can do that by creating a class that converts XML to JSON and then calling JSONGraphPlotter.plot. Instead of providing the responsibility of using the two classes to the client, we should be creating an abstraction, i.e., a single interface that allows doing both through a single function call.
Here we can use the Adapter Design Pattern. We will have an Adapter class XMLGraphPlotter that exposes a method plot which takes XML data as parameter. The XMLGraphPlotter.plot method would internally transform the XML data into JSON and call the JSONGraphPlotter.plot method with the JSON data. The client can work with the XMLGraphPlotter class to plot graphs with XML data without any change required in the JSONGraphPlotter class.
There can be other design problems related to class structuring as well based on our design requirements. We may want to use one of the structural design patterns based on the use case.
We will learn about the Structural Design Patterns in further articles.
Behavioral Design Patterns
Behavioral design patterns deal with the assignment of responsibilities and communication between objects. It focuses on how the objects can interact or communicate with each other while still being loosely coupled.
Let us look at a design problem related to behavior of objects.
Example
Let's say we want to design a Parking Lot. The Parking Lot service should assign a vacant parking slot to any vehicle that wants to park.
The algorithm to assign a free parking spot to a vehicle may vary based on different criteria. There can be new requirements as well based on which the algorithm to assign a vacant parking spot to a vehicle may vary.
Instead of putting conditionals everywhere in our code, we can apply the Strategy Design Pattern. This pattern allows us to switch the behavior between a family of algorithms during runtime. We can create different Strategy classes based on the same interface. Each strategy class would be used to implement a different algorithm. The clients would be able to pass any strategy as required.
There can be other design problems related to the behavior of an object or communication between the objects as well based on our design requirements. We may want to use one of the behavioral design patterns based on the use case.
We will learn about the Behavioral Design Patterns in further articles.
I hope that you found this tutorial helpful. We will be creating more such tutorials on Design Patterns and Low-Level Design in the near future. Please support us by sharing this article with your friends on Whatsapp and on social media.