Design patterns are a collection of standard solutions to solve software development design problems. Software engineers need to know how to use design patterns as essential tools. They can utilize design patterns to discover elegant solutions for commonly occurring challenges in software design, just as architects in building architecture learn to address design difficulties using established patterns. The efficiency, scalability, and readability of code are all improved by comprehending and utilizing these patterns.
The three categories that these patterns fall into are creational, behavioural, and structural. To keep things brief, we’ll define these categories and explore examples that fall into these categories over a series.
Creational Pattern: These patterns address procedures for creating objects. They assist in crafting objects in a way that makes sense for the scenario. The simplest method of creating objects may lead to problems with design or more complexity in a design. This issue is resolved by creational design patterns, which regulate the creation process. Some of the popular creational patterns include:
- Builder
- Factory
- Abstract Factory
- Singleton
- Prototype
Structural Pattern: The composition of classes and objects is central to structural patterns. They support the formation of complex structures with classes and objects. Structural patterns use inheritance to create interfaces and implementations. Several well-known patterns of structure include:
- Adapter
- Decorator
- Facade
Behavioural Pattern: a category of software design patterns that helps control how various system elements communicate and interact. Example includes:
- Observer
- Strategy
- State
- Iterator
We’ll start the series with one of the creational patterns: Singleton.
Singleton Pattern
Singleton pattern ensures that the instance of a class is instantiated once and provides global access to the instance. It is part of the creational design pattern category.
Motivation
When building a system, sometimes we want the system to have access to a shared resource, e.g. a database and a logging system. To make this work as intended, a single instance of an object or resource must exist at a time. Instantiating a constructor would not work because it returns a new instance of an object whenever it is called. Singleton pattern solves for us by ensuring that the regular constructor is called only once under the hood, and that is when it is being created for the first time.
Singleton pattern also provides global access to an object by making an object available everywhere in a program, which helps with;
- Consistency: there should be at most one instance of a database in a system; if there is, it would further improve the level of consistency in the application's behaviour.
- It helps to conserve resources
- It also helps to control the means to access an object.
public class DataBaseService {
public DataBaseService(){
}
}
Creating a DataBaseService class like the example above,
every time the class is instantiated, a new instance of the DatabaseService class is created, as shown below, which makes the class inconsistent for the intended use.
dataBase3: singleton.DataBaseService@135fbaa4
dataBase4: singleton.DataBaseService@45ee12a7
This is the implementation of a singleton pattern with respect to multithreaded environments like a Java program. This code below exposes a getInstance() method, which is called when we want to access the DataBaseService object, and it handles creating or providing an existing instance of the DataBaseService object. The private constructor in the class ensures the creation of an object using the regular constructor calling is controlled internally by the class. If dataBaseService is null, the class has not been created before and the regular constructor will be called.
public class DataBaseService {
private static volatile DataBaseService dataBaseService = null;
private DataBaseService() {
if (dataBaseService != null) {
throw new RuntimeException("There can only be one instance of Database");
}
}
public static DataBaseService getInstance() {
if (dataBaseService == null){
synchronized (DataBaseService.class) {
if (dataBaseService == null) {
dataBaseService = new DataBaseService();
}
}
}
return dataBaseService;
}
}
When we want to access the DataBaseService, we simply call the getInstance method, which creates or returns one instance of the class.
DataBaseService dataBaseService1 = DataBaseService.getInstance();
DataBaseService dataBaseService2 = DataBaseService.getInstance();
System.out.println("dataBaseService1: "+ dataBaseService1);
System.out.println("dataBaseService2: " + dataBaseService2);
When the two instances are logged, it is evidently the same, which shows that our singleton pattern is properly implemented as shown below.
dataBaseService1: singleton.DataBaseService@135fbaa4
dataBaseService12: singleton.DataBaseService@135fbaa4
Advantages
- It is sure that there is only one instance of a class.
- You can provide a global instance to a class.
Disadvantages
- Violates single responsibility principles
Use cases
Databases
Logging system
Real-Life Examples
Java Runtime Library
Conclusion
Singleton Pattern's approach to instance control makes it significant, but it's equally crucial to understand its strengths and weaknesses. Here's a link to check the full implementation.
Shout out to Markus Spiske for the cover photo.
Top comments (0)