要让高层模块依赖抽象接口而非具体实现,需通过契约明确行为规范,高层模块仅使用接口类型声明变量和参数,实现类由外部注入(如构造函数或Setter),运行时绑定由DI容器或配置决定。

要让高层模块依赖抽象接口而不是具体实现,关键在于把“谁创建、谁调用、谁绑定”这三个环节从硬编码中抽离出来,转而通过契约(接口或抽象类)来组织关系。
定义清晰的抽象接口
接口不是为了写而写,而是提炼出稳定不变的行为契约。比如一个订单服务,不定义 OrderServiceImpl,而是先定义 OrderService 接口,只暴露 create()、cancel() 等业务语义明确的方法。所有实现类——无论是数据库版、内存版还是模拟版——都必须遵守这个契约。
高层模块只声明接口类型变量
在业务逻辑类(如 PaymentProcessor)中,成员变量、方法参数、返回类型都使用接口,而不是 new XxxServiceImpl() 或直接引用实现类名:
- ✅ 正确:private final OrderService orderService;
- ❌ 错误:private final OrderServiceImpl orderService;
- ✅ 方法签名应为:public void process(OrderService service)
- ❌ 不应是:public void process(OrderServiceImpl service)
实现类与高层模块解耦,由外部注入
实现类不主动被高层模块 new 出来,而是通过外部机制“送进来”。常用方式有:
- 构造函数注入:在创建高层模块实例时,把实现类对象传入构造器
- Setter 注入:提供 public setXxx(Interface impl) 方法供容器或测试代码设置
- 工厂或 Service Locator(较重,适合遗留系统过渡)
这样,高层模块完全不知道也不关心底层是 MySQL 还是 Redis 实现,只要它实现了接口就行。
运行时绑定由框架或配置决定
真正选择哪个实现类,交给 Spring、Guice 等 DI 容器,或通过配置文件、环境变量控制。例如在 Spring Boot 中,只需加 @Service 和 @Primary,或用 @Profile("test") 切换不同实现。高层模块代码零修改,就能切换数据源、日志组件、支付网关等。










