为何单例模式违反依赖倒置原则(DIP)?
为什么单例的
getInstance()会违反依赖倒置原则(DIP) 先明确依赖倒置原则的核心:
- 高层模块不应该依赖低层模块,两者都应该依赖抽象
- 抽象不应该依赖细节,细节应该依赖抽象
我们先看最常见的单例实现和客户端调用代码:
典型单例实现
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() {} public static Singleton getInstance() { return INSTANCE; } public void doBusiness() { // 业务逻辑 } }
客户端调用
public class Client { public void execute() { Singleton singleton = Singleton.getInstance(); singleton.doBusiness(); } }
问题出在哪?
客户端代码里直接写了Singleton.getInstance(),这意味着客户端必须明确依赖Singleton这个具体类,而不是某个抽象接口:
- 客户端的
execute()方法硬编码了具体类的静态方法,它知道“单例的类型是Singleton”这个细节 - 如果以后需要替换这个单例的实现(比如换成另一个实现相同业务逻辑的类,或者改成非单例),客户端代码必须修改
- 高层的
Client模块直接依赖低层的Singleton具体类,完全没有依赖抽象,这直接违反了DIP的核心要求
为什么getInstance()是根源?
getInstance()是具体类的静态方法,它不属于任何抽象接口。客户端要获取实例,就必须直接引用这个具体类——没有抽象层作为中间层,自然无法做到“依赖抽象而非细节”。
怎么改才能符合DIP?
我们可以引入抽象接口,同时把具体类的依赖从客户端中剥离:
1. 定义抽象接口
public interface BusinessService { void doBusiness(); }
2. 让单例实现抽象接口
public class SingletonService implements BusinessService { private static final SingletonService INSTANCE = new SingletonService(); private SingletonService() {} // 这里返回抽象类型,而非具体类 public static BusinessService getInstance() { return INSTANCE; } @Override public void doBusiness() { // 业务逻辑 } }
3. 客户端依赖抽象,通过注入获取实例
public class Client { private final BusinessService service; // 构造注入:客户端只知道依赖BusinessService抽象,不知道具体实现 public Client(BusinessService service) { this.service = service; } public void execute() { service.doBusiness(); } }
4. 初始化时绑定具体实现
public class App { public static void main(String[] args) { // 只有初始化层知道具体的单例实现 BusinessService service = SingletonService.getInstance(); Client client = new Client(service); client.execute(); } }
这样修改后,Client这个高层模块只依赖BusinessService抽象,完全不知道具体的单例类存在;SingletonService这个低层模块依赖BusinessService抽象,符合DIP的要求。后续如果要替换服务实现,只需要修改App里的初始化代码,Client完全不用动。
内容的提问来源于stack exchange,提问作者김유담
相关产品推荐
相关产品推荐

