当类超500行时,需判断是否同时承担状态维护、业务规则执行、外部交互三类职责;若public方法操作不同字段、构造函数注入无关依赖、测试需mock多协议接口,则违反单一职责,应按变化原因拆分为OrderValidator、OrderPricingEngine、OrderRepository,并由PlaceOrderUseCase协调。

一个类超过 500 行时,该怎么判断它是否违反了单一职责?
行数只是表象,关键看它是否同时承担「状态维护」「业务规则执行」「外部交互」三类职责。比如一个 OrderProcessor 类里既有 calculateDiscount()、又有 sendEmailNotification()、还混着 saveToDatabase(),这就已经越界了——折扣逻辑属于领域规则,发邮件是通知机制,落库是持久化策略,三者变化原因完全不同。
实操建议:
- 逐个检查类中所有 public 方法:它们修改的是同一组字段吗?如果不是(比如有些方法只读
orderItems,另一些只写statusLog),说明职责在撕裂 - 看构造函数参数:如果注入了
EmailService、PaymentGateway、InventoryClient三类完全无关的依赖,大概率该拆 - 搜索类名在测试文件中的出现频率:如果一个
OrderProcessorTest里要 mock 7 个不同协议的接口,就是警报信号
把 validate()、applyRules()、persist() 拆到哪几个类里才合理?
不是按动词拆,而是按「谁会因为什么理由修改它」来分。验证逻辑常随合规要求变,应独立为 OrderValidator;业务规则(如满减、积分抵扣)由运营驱动,适合抽成 OrderPricingEngine;而 persist() 这种和存储细节强绑定的操作,应交给 OrderRepository(注意:不是 DAO,它应封装事务边界和聚合根一致性)。
常见错误:
- 把
OrderValidator做成静态工具类:失去可替换性,无法针对测试场景注入自定义校验规则 - 让
OrderPricingEngine直接调用database.query():它不该知道数据从哪来,应通过PriceRuleProvider接口获取策略 - 在
OrderRepository里写 SQL 字符串拼接:应使用 QueryDSL、jOOQ 或至少预编译语句,避免泄露数据库方言
拆分后如何保证原有调用链不散架?
引入协调层(orchestrator),但别叫 OrderService——这名字太泛。直接命名为 PlaceOrderUseCase,它只做三件事:接收参数、依次委托给 OrderValidator → OrderPricingEngine → OrderRepository、返回结果。这个类不应含任何分支逻辑或计算,否则又成了新上帝类。
关键约束:
-
PlaceOrderUseCase的构造函数只接受三个明确角色的接口,不接受原始DataSource或EntityManager - 所有被委托类必须能单独单元测试:比如
OrderValidatorTest不依赖数据库,OrderPricingEngineTest不依赖网络 - 如果某步失败需回滚,不要在
PlaceOrderUseCase里写 try-catch-rollback:交由框架事务管理器(如 Spring @Transactional)或显式 Saga 编排
哪些看似合理的“内聚”其实是伪内聚,拆了反而更糟?
比如把 toString()、equals()、hashCode() 抽到独立工具类——这是反模式。这三个方法强绑定于对象的字段构成,挪走后一旦新增字段,极易漏改 equals 逻辑,且 IDE 自动生成功能失效。同理,toDTO() 如果只是字段一一映射,也该留在原实体内(或用 MapStruct 等编译期生成)。
真正该警惕的是「技术性内聚」:比如一个类同时包含 JSON 序列化逻辑和业务计算,仅仅因为「都用到了 Jackson 注解」就认为它们天然该在一起——注解只是配置,不是职责依据。
复杂点往往藏在边界上:当一个类既处理用户输入(含格式转换)、又执行核心算法、还要生成审计日志,最容易误判为「一整条流水线所以不能拆」。其实输入解析应归 OrderRequestParser,算法归 OrderCalculator,日志归 AuditLogger,三者通过不可变的 OrderCommand 对象通信。漏掉这个中间对象,拆分就会变成接口爆炸。

















