正确使用Constructor Injection:如何处理调用类的依赖问题?
构造函数注入的问题解答
场景回顾
现有两个职责与依赖完全不同的Rule类,为了保证单元测试性,最初采用构造函数注入依赖:
class RuleA implements MyInterface { private DependencyA_1 dependencyA1; private DependencyA_2 dependencyA2; public RuleA(DependencyA_1 dependencyA1, DependencyA_2 dependencyA2) { this.dependencyA1 = dependencyA1; this.dependencyA2 = dependencyA2; } public Result execute() { // ...业务逻辑 } }
class RuleB implements MyInterface { private DependencyB_1 dependencyB1; private DependencyB_2 dependencyB2; public RuleB(DependencyB_1 dependencyB1, DependencyB_2 dependencyB2) { this.dependencyB1 = dependencyB1; this.dependencyB2 = dependencyB2; } public Result execute() { // ...业务逻辑 } }
通过工厂类根据字符串创建对应Rule实例时,发现工厂需要知晓所有Rule的依赖,存在强耦合;给Rule添加无参构造(硬编码生产依赖)后,工厂及依赖它的类又无法做单元测试,因为会调用依赖外部模块的生产构造逻辑。
问题解答
1. 为测试和生产用不同构造函数是否是良好模式?
这种做法不是好模式,原因如下:
- 违反单一职责:Rule类原本只负责业务逻辑,现在还要操心生产依赖的实例化,职责变杂。
- 测试受阻:无参构造硬绑定生产依赖,导致工厂和上层代码无法隔离外部资源(比如数据库),单元测试只能被迫变成集成测试,失去了单元测试快速、隔离验证的价值。
- 维护麻烦:如果生产依赖的创建方式改了,所有Rule类的无参构造都要跟着改,配置逻辑分散,容易漏改。
2. 如何解决构造函数选择的两难问题?
不用依赖DI框架也能搞定,核心思路是把依赖创建逻辑从Rule类和工厂类里抽出来,具体有两种可行方案:
方案1:给工厂注入依赖提供者
专门定义依赖提供者类,让工厂只负责根据类型选Rule,依赖由提供者来创建:
// 定义统一的依赖提供者接口 interface RuleDependencyProvider { MyInterface createRule(); } // RuleA的专属提供者,负责创建生产环境的RuleA实例 class RuleADependencyProvider implements RuleDependencyProvider { @Override public MyInterface createRule() { return new RuleA(new MyProductiveDependencyA1(), new MyProductiveDependencyA2()); } } // RuleB的专属提供者 class RuleBDependencyProvider implements RuleDependencyProvider { @Override public MyInterface createRule() { return new RuleB(new MyProductiveDependencyB1(), new MyProductiveDependencyB2()); } } // 改造后的工厂,完全解耦Rule的依赖细节 class Factory { private final Map<String, RuleDependencyProvider> providerMap; // 生产环境初始化时,把类型和提供者的映射传进来 public Factory(Map<String, RuleDependencyProvider> providerMap) { this.providerMap = providerMap; } public MyInterface createClass(String type) { RuleDependencyProvider provider = providerMap.get(type); if (provider == null) { throw new IllegalArgumentException("未知规则类型: " + type); } return provider.createRule(); } }
这个方案的好处:
- 工厂彻底和Rule的依赖脱钩,不需要知道任何Rule依赖的细节。
- 单元测试工厂时,只需要模拟
RuleDependencyProvider返回Mock的Rule实例就行,完全隔离外部依赖。 - 生产依赖的配置集中在提供者类里,修改时只动对应提供者,维护更方便。
方案2:用静态工厂方法替代无参构造(次选)
如果不想加太多类,可以在Rule里加静态方法专门用于生产环境,只保留带参数的构造函数:
class RuleA implements MyInterface { private DependencyA_1 dependencyA1; private DependencyA_2 dependencyA2; // 只保留带参数的构造函数,强制依赖注入 public RuleA(DependencyA_1 dependencyA1, DependencyA_2 dependencyA2) { this.dependencyA1 = dependencyA1; this.dependencyA2 = dependencyA2; } // 生产环境专用的静态创建方法 public static RuleA createForProduction() { return new RuleA(new MyProductiveDependencyA1(), new MyProductiveDependencyA2()); } public Result execute() { // ...业务逻辑 } }
然后工厂调用静态方法:
class Factory { public MyInterface createClass(String type) { if ("A".equals(type)) { return RuleA.createForProduction(); } if ("B".equals(type)) { return RuleB.createForProduction(); } throw new IllegalArgumentException("未知规则类型: " + type); } }
这种方式比无参构造好,因为明确区分了生产用的创建逻辑,但工厂还是和Rule的生产依赖有耦合,单元测试时需要Mock静态方法(相对麻烦),所以优先选方案1。
关于集成测试的疑问
不是只能接受集成测试,上面的方案都能让你继续做单元测试。就算需要做集成测试,也不算不良设计——集成测试适合验证整体流程,单元测试适合快速验证单个类的逻辑,两者是互补的,不是非此即彼的关系。
内容的提问来源于stack exchange,提问作者user2874177
相关产品推荐
相关产品推荐

