204 lines
6.0 KiB
Plaintext
204 lines
6.0 KiB
Plaintext
The Singleton design pattern is a creational pattern.
|
||
It ensures that only one class instance is created and provides a global point of access to it.
|
||
There are several ways to implement a Singleton in Java, and the debate about the best approach has yet to be settled.
|
||
|
||
This rule marks all classes that are considered as Singletons, regardless of how they are implemented.
|
||
This helps developers identify where in the code Singletons are used.
|
||
Singletons should be reviewed to confirm whether a Singleton is truly necessary and whether the selected implementation is the most suitable for the context.
|
||
|
||
Every Singleton implementation has its advantages and disadvantages.
|
||
This rule exists to bring attention to them so that informed decisions can be made.
|
||
|
||
== Why is this an issue?
|
||
|
||
While the Singleton pattern can be useful in certain situations, overusing it can have several drawbacks:
|
||
|
||
* Tight coupling: The Singleton pattern can create tight coupling between the Singleton class and other classes that use it, making the code difficult to maintain and modify.
|
||
* Global state: The Singleton pattern can create a global state, making it difficult to manage the state of the application and leading to unexpected behavior.
|
||
* Testing: The Singleton pattern can make it difficult to test classes that depend on the Singleton, as the Singleton cannot be easily substituted with a mock object.
|
||
* Scalability: The Singleton pattern can make it difficult to scale an application, as it can create a bottleneck if multiple threads try to access the Singleton concurrently.
|
||
* Dependency injection: The Singleton pattern can make it difficult to use dependency injection frameworks, as the Singleton instance is usually created statically.
|
||
|
||
In general, the Singleton pattern should be used sparingly and only in situations where it provides a clear benefit over other patterns or approaches.
|
||
It is important to consider the drawbacks and tradeoffs of using the Singleton pattern before incorporating it into an application.
|
||
|
||
=== What is the potential impact?
|
||
|
||
==== Enum Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public enum EnumSingleton {
|
||
|
||
INSTANCE;
|
||
|
||
private EnumSingleton() {
|
||
// Initialization code here...
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantages*:
|
||
|
||
This implementation is thread-safe by default because the initialization of an Enum value is guaranteed to be thread-safe and atomic.
|
||
|
||
The Enum Singleton implementation allows for lazy initialization while also providing thread-safety guarantees.
|
||
|
||
==== Bill Pugh Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class BillPughSingleton {
|
||
|
||
private BillPughSingleton(){}
|
||
|
||
private static class SingletonHelper {
|
||
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
|
||
}
|
||
|
||
public static BillPughSingleton getInstance() {
|
||
return SingletonHelper.INSTANCE;
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantages*:
|
||
|
||
The instance is created only at the first call of the `getInstance()` method.
|
||
|
||
This implementation is thread-safe.
|
||
|
||
==== Thread Safe Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class ThreadSafeSingleton {
|
||
|
||
private static ThreadSafeSingleton instance;
|
||
|
||
private ThreadSafeSingleton(){}
|
||
|
||
public static synchronized ThreadSafeSingleton getInstance() {
|
||
if (instance == null) {
|
||
instance = new ThreadSafeSingleton();
|
||
}
|
||
return instance;
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantage*:
|
||
|
||
This implementation is thread-safe.
|
||
|
||
*Disadvantage*:
|
||
|
||
It reduces the performance because of the cost associated with the synchronized method.
|
||
To avoid this extra overhead every time, double-checked locking principle should be used.
|
||
|
||
==== Static Block Initialization Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class StaticBlockSingleton {
|
||
|
||
private static StaticBlockSingleton instance;
|
||
|
||
private StaticBlockSingleton(){}
|
||
|
||
static {
|
||
try {
|
||
instance = new StaticBlockSingleton();
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("Exception while creating singleton instance");
|
||
}
|
||
}
|
||
|
||
public static StaticBlockSingleton getInstance() {
|
||
return instance;
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantage*:
|
||
|
||
Compared to the Eager Initialization, this implementation provides options for exception handling.
|
||
|
||
*Disadvantage*:
|
||
|
||
The instance is created even if it's never used, like for the Eager Initialization implementation.
|
||
|
||
==== Eager Initialization Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class EagerInitializedSingleton {
|
||
|
||
private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();
|
||
|
||
private EagerInitializedSingleton() {}
|
||
|
||
public static EagerInitializedSingleton getInstance() {
|
||
return instance;
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantage*:
|
||
|
||
This implementation is thread-safe, as the instance variable is initialized when the class is loaded.
|
||
|
||
*Disadvantages*:
|
||
|
||
The instance is created even if it's never used, which can be wasteful in terms of memory usage.
|
||
However, if the Singleton is expected to be used frequently or is not too memory-intensive, Eager Initialization can be a good choice.
|
||
|
||
This implementation doesn’t provide any options for exception handling.
|
||
|
||
==== Lazy Initialization Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class LazyInitializedSingleton {
|
||
|
||
private static LazyInitializedSingleton instance;
|
||
|
||
private LazyInitializedSingleton(){}
|
||
|
||
public static LazyInitializedSingleton getInstance() {
|
||
if (instance == null) {
|
||
instance = new LazyInitializedSingleton();
|
||
}
|
||
return instance;
|
||
}
|
||
}
|
||
----
|
||
|
||
*Advantage*:
|
||
|
||
This implementation works fine in the case of the single-threaded environment.
|
||
|
||
*Disadvantage*:
|
||
|
||
This implementation is not thread-safe if multiple threads are at the same time in the `if` condition.
|
||
|
||
==== Public Static Field Implementation
|
||
|
||
[source,java]
|
||
----
|
||
public class PublicStaticSingleton {
|
||
|
||
public static final PublicStaticSingleton INSTANCE = new PublicStaticSingleton();
|
||
|
||
private PublicStaticSingleton() {}
|
||
}
|
||
----
|
||
|
||
*Advantage*:
|
||
|
||
This implementation is thread-safe.
|
||
|
||
*Disadvantage*:
|
||
|
||
This implementation does not allow lazy initialization: the constructor runs as soon as the class is initialized.
|