封装与依赖注入协同降低耦合:封装保护内部状态边界,依赖注入理清外部协作关系;二者配合实现“里子”安全、“面子”清晰。

封装和依赖注入不是互斥手段,而是协同发力的两个关键层:封装保护内部状态与行为边界,依赖注入管理外部协作关系。真正降低耦合,靠的是二者配合——用封装守住类的“里子”,用依赖注入理清类的“面子”。
把依赖声明为接口,而非具体实现
这是解耦的第一步。比如订单服务需要发通知,不要写 private EmailService emailService,而应定义 private NotificationService notificationService(NotificationService 是接口)。这样 UserService 就不关心底层是邮件、短信还是站内信,也不绑定任何 SDK 或配置细节。
常见做法包括:
- 仓储层统一用 UserRepository 接口,上层服务完全不感知 MySQL、Redis 或 Mock 实现
- 支付模块暴露 PaymentService.pay(),隐藏微信、支付宝等具体 SDK 的调用逻辑
- Android 中状态管理不直接暴露 LiveData<Boolean> 字段,而是提供 isLoginReady() 和 observeLoginState() 等语义化方法
用构造器注入强化封装契约
构造器注入天然契合封装原则:它强制调用方在创建对象时就明确提供必需依赖,同时可将字段设为 final,防止后续被意外修改或置空。这既保障了对象初始化完整性,也避免了 getter/setter 泛滥带来的“伪封装”。
立即学习“Java免费学习笔记(深入)”;
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
例如:
- UserService 的构造器只接收 UserRepository 接口,不暴露 setter;内部字段 final,不可重赋值
- 若某依赖可选(如异步日志上报),可用 setter 注入,但必须内置默认行为或空安全检查,不破坏主流程稳定性
- 避免字段注入(如 Spring 的 @Autowired private XxxService),它绕过构造逻辑,容易导致对象半初始化或测试时难模拟
在封装方法中收拢多变逻辑,隔离依赖变化
依赖注入解决的是“用谁”,封装解决的是“怎么用”。把业务规则、计算策略、数据组装等易变逻辑全封装进方法体,外部只认语义化接口,不拼字段、不写 if-else 判断实现类型。
比如:
- 优惠计算统一走 order.getFinalPrice(),内部根据会员等级、时段、品类动态选择 PricingStrategy 实现,调用方无感知
- 库存扣减不暴露 stock -= 1,而是提供 decreaseStock(int quantity),内部嵌入超卖校验、分布式锁、库存预警等横切逻辑
- getter 不只是返回值,可加入访问审计、懒加载(首次调用才查缓存)、过期刷新等封装行为
警惕“有注入没封装”的陷阱
只做依赖注入,不加强封装,耦合依然顽固。典型反例:
- 注入了一个 ArrayList<Order>,但 getter 直接返回原引用——外部可随意 add/remove,破坏内部一致性
- 注入了 ConfigService,但各处都手动调 config.get("timeout.ms"),一旦 key 改名或格式升级,全项目散弹式报错
- 自动生成的 setter/getter 没加校验、没处理并发、没隔离副作用,等于给 public 字段套了个方法马甲
真正有效的封装,要求每个注入点都配套访问契约:谁可以改、怎么改、改了触发什么、边界在哪——这些都要在方法签名和实现中主动设计,而不是交给调用方自由发挥。

















