The first design pattern examined in this course is the
Singleton Design Pattern. The
Singleton Design Pattern is used when there should only be one instance of a given class. It uses static, class methods and private
constructors[1] to strictly control creation of new instances of the class.
In this module, you will learn:
- The ten elements that distinguish a pattern
- The intent of the Singleton pattern
- When to use the Singleton pattern
- The structure of the Singleton pattern
- How the Singleton pattern is used in the real world
The Singleton Design Pattern is a software design pattern that ensures a class has only one instance and provides a global point of access to that instance. It originated from the need to manage shared resources, like configuration settings or connection pools, without creating multiple instances that could lead to resource contention or inconsistent states. The concept of a singleton can be traced back to the early days of object-oriented programming and was first formally documented in the influential book "Design Patterns: Elements of Reusable Object-Oriented Software" by the "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in 1994.
The design pattern gained popularity due to its simplicity and effectiveness in ensuring a single point of access to particular resources. In the 1990s, as software development began to emphasize reusable and maintainable code, the Singleton Design Pattern emerged as a practical solution for managing global application states. It was particularly useful in scenarios where creating multiple instances of a class could cause issues, such as when managing system configurations, logging mechanisms, or hardware interfaces.
Despite its widespread use, the Singleton Design Pattern has also faced criticism and evolved over time. Critics argue that it can lead to problems such as hidden dependencies, difficulties in unit testing, and limited flexibility in certain scenarios. To address these concerns, modern software development practices often recommend using dependency injection and other design patterns to achieve similar outcomes with better testability and modularity. Nevertheless, the Singleton Design Pattern remains a fundamental concept in the field of software design, demonstrating the enduring importance of creating efficient and maintainable code.
In the domain of software design, the Singleton class holds a distinctive position. Based on the provided definition, a Singleton class is one that embodies the principle of exclusivity in object instantiation. This exclusivity ensures that only one instance, or object, of this class is created throughout the entire runtime of a software application.
This singular object serves a crucial purpose. By its very nature of being the sole instance, it becomes a global resource, accessible to all clients or components within the system. Such a global facility ensures a centralized point of access, guaranteeing consistency and coherence in operations that rely on this specific object.
The Singleton pattern, from which this class derives its name, is thus primarily employed to maintain this controlled access and ensure that no duplicate or parallel instances emerge, which could otherwise lead to discrepancies or conflicts within the system.
In essence, the Singleton class epitomizes a design approach where exclusivity and centralized access are paramount, ensuring that the entire system benefits from a consistent and unified resource.
For that reason, computer-generated random numbers should really be called
pseudo-random numbers.
In most algorithms for generating a sequence of pseudo-random numbers, you start with a seed value and transform it to obtain the first value of the sequence. Then you apply the transformation again for the next value, and so on.
The Java library uses a
linear congruential generator and the seed is transformed according to the equation
seed = (seed * 25214903917 + 11) % 248
Typically, the seed of a random number generator is set to the time at its construction, to some value obtained by measuring the time between user keystrokes, or even to the input from a hardware device that generates random noise. However, for debugging purposes, it is often helpful to set the seed to a known quantity. Then the same program can be run multiple times with the same seed and thus with the same sequence of pseudo-random numbers. For this debugging strategy to be effective, it is important that there is one global random number generator. Let us design a class SingleRandom that provides a single random number generator. The key to ensuring that the class has a single instance is to make the constructor private. The class constructs the instance and returns it in the static getInstance method.
public class SingleRandom{
private SingleRandom() { generator = new Random(); }
public void setSeed(int seed) { generator.setSeed(seed); }
public int nextInt() { return generator.nextInt(); }
public static SingleRandom getInstance() { return instance; }
private Random generator;
private static SingleRandom instance = new SingleRandom();
}
Note that the static field instance stores a reference to the unique SingleRandom object. Recall that a static field is merely a
global variable and in Java, every field must be declared in some class.
We find it convenient to place the instance field inside the SingleRandom class itself.
The global variable is one of the old programming practices that creates side effects for the object-oriented programmer. Global variables tie classes into their context, undermining encapsulation. A class that relies on global variables becomes impossible to pull out of one application and use in another, without first ensuring that the new application itself defines the same global variables. Although this is undesirable, the unprotected nature of global variables can be a greater problem. Once you start relying on global variables, it is perhaps just a matter of time before one of your libraries declares a global that clashes with another declared elsewhere. By using globals, though, you potentially leave your users exposed to new and interesting conflicts when they attempt to deploy your library alongside others. Globals remain a temptation, however.
This is because there are times when the inherent negative side effects of using global access seems a price worth paying in order to give all your classes access to an object. Namespaces provide some protection from this. You can at least scope variables to a package, which means that third-party libraries are less likely to clash with your own system. Even so, the risk of collision exists within the namespace itself.