高内聚低耦合需通过明确职责边界、受控依赖传递和克制继承实现;类须遵循单一职责原则,多态逻辑应拆为策略接口,工具方法应独立,依赖须显式注入且不越界,优先组合而非继承,包结构应按功能与模块清晰分层。

高内聚低耦合不是靠感觉写出来的,是靠明确的职责边界、受控的依赖传递和克制的继承使用做出来的。
类的设计必须遵循单一职责原则(SRP)
一个类只做一件事,这件事要能用一句话说清,比如“解析 JSON 字符串为 User 对象”或“将订单状态从 CREATED 变更为 PAID”。一旦发现类里出现 if (type == "email") 或 switch (format) 处理多种逻辑,大概率已经违反 SRP。
- 把多态逻辑拆出去:用策略接口(如
NotificationSender)+ 实现类(EmailSender、SmsSender),而不是在同一个类里堆sendEmail()和sendSms() - 工具方法别塞进业务类:日期格式化、字符串脱敏等应抽成独立的
DateFormatter或SensitiveMasker,而非让OrderService自己new SimpleDateFormat(...) - 测试时如果一个单元测试要 mock 5 个协作对象,说明这个类承担了太多角色,该拆了
依赖注入要显式、可替换、不越界
任何外部依赖(数据库、HTTP 客户端、配置)都不能在类内部 new,也不能硬编码为静态单例。依赖必须通过构造函数或 setter 注入,且类型应为接口而非具体实现。
- 构造函数注入优于
@Autowired字段注入:前者强制调用方提供依赖,后者容易掩盖空指针风险 - 避免跨层传递依赖:Service 层不该持有
JdbcTemplate,而应依赖OrderRepository接口;Controller 不该知道RedisTemplate,只调用CacheService.get(key) - 测试时能直接传入
new MockOrderRepository()或new InMemoryOrderRepository(),就说明依赖设计合格
继承要谨慎,优先组合 + 接口
Java 中滥用 extends 是耦合的温床。子类一旦继承父类,就绑定了它的字段、方法签名甚至部分实现细节,改父类极易破环子类行为。
- 当两个类只有“行为相似”,但没有明确的“is-a”关系(比如
Car和Bike都能start()),就用接口(Movable)而不是抽象父类 - 模板方法模式可用,但钩子方法(
beforeSave())必须是protected且有默认空实现,避免子类被迫重写 - 组合优于继承:把通用能力封装成组件(如
IdGenerator、RetryPolicy),在需要的类中作为private final字段持有,比继承更灵活、更易测
包结构反映模块边界,避免循环依赖
包名不是随便起的命名空间,而是模块职责的物理体现。一个包下的所有类应该共同完成一件明确的事,且对外只暴露稳定接口。
- 禁止
com.example.order包里混着OrderController、OrderMapper、OrderRedisCache—— 这会把表现层、数据层、缓存策略全搅在一起 - 推荐按功能分层再按模块切分:如
com.example.order.api(DTO/Controller)、com.example.order.domain(Entity/Aggregate/DomainService)、com.example.order.infra(JDBC/Redis/Kafka 实现) - 用
mvn compile后检查target/classes下的 class 文件路径,如果发现 A 包的类 import 了 B 包的类,B 包又 import 了 A 包的类,就是循环依赖,必须引入中间接口或调整包职责
真正难的不是写出符合规范的代码,而是在需求压过来时,忍住不把三个功能塞进一个类、不为了“快”而直接 new 一个工具类、不在接口里加一个看似无害的默认方法——这些瞬间的选择,才决定代码半年后还能不能动。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











