I learned that In singleton pattern, the client depends on the concrete class in the dependency relationship. So singleton pattern violates DIP. Stackoverflow
Yes, you are correct. In the Singleton design pattern, the client depends on the concrete class, which can violate the Dependency Inversion Principle (DIP). Let's break down why this is the case and explore potential solutions.
Understanding the Issue
Singleton Pattern and DIP
⦿ Singleton Pattern: Ensures a
class has only one instance and provides a global point of access to
it.
⦿ Dependency Inversion Principle (DIP): High-level modules should
not depend on low-level modules. Both should depend on abstractions.
Abstractions should not depend on details. Details should depend on
abstractions.
In the Singleton pattern, you typically access the single instance via a
static method, which directly couples the client code to the concrete class.
This direct dependency on the concrete class means the client cannot easily
be changed to use a different implementation without modifying the client
code itself, thus violating DIP.
Example
Let's look at a basic Singleton example and then refactor it to comply
with DIP.
Singleton Example (Violates DIP)
object MySingleton {
fun doSomething() {
// Implementation
}
}
class Client {
fun execute() {
MySingleton.doSomething() // Direct dependency on the concrete class
}
}
In this example, Client is directly dependent on MySingleton, which violates DIP because Client cannot be easily adapted to use a different implementation.
Refactoring to Comply with DIP
To comply with DIP, you can introduce an interface or an abstract class
that the Singleton class implements. The client code will then depend on
this abstraction rather than the concrete Singleton class.
Refactored Example
1.Define an Interface:
interface MyService {
fun doSomething()
}
2.Implement the Singleton:
object MySingleton : MyService {
override fun doSomething() {
// Implementation
}
}
3.Refactor the Client to Depend on the Abstraction::
class Client(private val myService: MyService) {
fun execute() {
myService.doSomething()
}
}
4.Provide the Dependency (e.g., via Dependency Injection)::
fun main() {
val myService: MyService = MySingleton
val client = Client(myService)
client.execute()
}
In this refactored example:
-
The
MyService
interface is introduced as an abstraction. -
MySingleton
implements theMyService
interface. -
The
Client
class now depends on theMyService
interface rather than theMySingleton
concrete class. -
The main function or dependency injection framework provides the
MyService
implementation to theClient
.
By introducing an abstraction and making the client depend on that abstraction, you adhere to the Dependency Inversion Principle. This makes your code more flexible and easier to test and maintain.
No comments: