依赖倒置原则要求高层模块与低层模块均依赖抽象,抽象不依赖细节;通过小而专注的接口定义业务契约(如ipaymentprocessor),构造注入实现解耦,避免硬编码和service locator,抽象粒度应贴合真实变化点。

依赖倒置原则(DIP)的核心是让高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节,细节依赖抽象。说白了,就是面向接口(或抽象类)编程,而不是面向具体实现编程——这样改底层逻辑时,上层代码不用动。
用接口定义行为契约
把稳定的需求提炼成接口,比如“支付”这个动作,定义为 IPaymentProcessor 接口,包含 Process() 方法。微信支付、支付宝、银行卡这些具体方式,都去实现它。上层订单服务只引用这个接口,不关心背后是哪个渠道。
- 接口要小而专注,一个接口只表达一类能力(如只做支付,不混入退款或查询)
- 避免在接口里暴露实现细节(比如不要叫 WeChatPayProcessWithCert())
- 接口命名体现业务意图,而不是技术路径(用 IPayment,别用 IHttpPaymentAdapter)
构造注入代替硬编码创建
高层模块不该自己 new 具体类。比如订单服务需要支付功能,就通过构造函数接收一个 IPaymentProcessor 实例:
- 运行时由容器(如 .NET DI、Spring、Autofac)注入实际类型,切换支付方式只需改配置
- 单元测试时可轻松传入 Mock 对象,验证逻辑是否正确,不依赖真实网络或第三方 SDK
- 避免使用 Service Locator 模式(如 ServiceLocator.GetService
() ),它隐藏依赖,不利于测试和维护
抽象粒度要贴合业务变化点
不是所有地方都要抽象,关键看哪部分容易变。比如日志记录方式可能从文件切到 Elasticsearch,那就抽象出 ILogger;但字符串拼接逻辑稳定,就没必要搞个 IStringConcatenator。
- 识别变化:哪些模块常被替换?哪些策略会随业务调整?这些就是抽象的信号
- 过度抽象反而增加理解成本,接口应服务于可维护性,不是为了“看起来高级”
- 抽象一旦确立,尽量保持稳定;若必须修改,优先考虑扩展(新增方法/接口),而非破坏性变更
依赖倒置不是教条,而是帮你在变与不变之间划清边界。写代码时多问一句:“如果明天这个功能换成另一种实现,我得改几处?”答案越少,说明抽象越到位。










