采用构造函数依赖注入后Main方法臃肿,求优化方案
嘿,我太懂这种糟心的情况了——把所有对象构建逻辑都堆在main方法里,瞬间就变成一团乱糟糟的“初始化面条代码”,后续维护起来简直头疼。既然你已经用构造函数实现了规范的依赖注入,那核心思路就是把对象组装的职责从main里抽离出来,给你几个适配你场景的实用方案:
方案1:用工厂类封装组装逻辑
最直接的方式是创建一个专门的工厂类,把所有对象的组装逻辑都放到这里,让main只负责启动应用。比如针对你的代码,我们可以写一个AppFactory:
public class AppFactory { // 对外暴露创建MyApp的入口方法 public static MyApp createMyApp() { // 从底层依赖开始构建,按依赖顺序来 Config config = Config.getInstance(); // 假设Config是单例或全局可获取 CustomChart customChart = new CustomChart(config.getChartOptionOne(), config.getChartOptionTwo()); // 构建子视图 MyModel model = getMyModel(); SubView subView1 = new SubView(model); SubView subView2 = new SubView(model, customChart); // 构建主视图和最终的MyApp View view = new View(subView1, subView2); return new MyApp(model, view); } // 把模型读取的逻辑单独抽出来,方便复用或修改 private static MyModel getMyModel() { return MyModelFileReader.getModel(Config.getDefaultFile()); } }
然后你的main方法就会变得无比清爽:
public static void main(String[] args) { MyApp myApp = AppFactory.createMyApp(); myApp.start(); }
这个方案的好处是:
- 把复杂的组装逻辑封装起来,main保持“启动器”的单一职责
- 工厂里的方法可以单独编写单元测试,依赖变化时只需要修改工厂类
- 后续如果需要不同配置的MyApp,只需要在工厂里加个
createMyAppWithCustomConfig()方法就行
方案2:用依赖注入容器管理依赖(适合中大型项目)
如果你的项目后续会变得更复杂,依赖关系越来越多,那直接用DI容器会更省心——不管是Spring、Guice这类成熟框架,还是自己手写一个简单的容器,都能帮你自动管理对象的创建和依赖注入。
举个手写简易DI容器的例子:
public class SimpleDIContainer { private final Map<Class<?>, Object> singletonInstances = new HashMap<>(); // 用名称区分同类型不同实例的情况,比如两个SubView private final Map<String, Object> namedInstances = new HashMap<>(); public void init() { // 按依赖顺序注册所有对象 registerSingleton(Config.class, Config.getInstance()); MyModel model = MyModelFileReader.getModel(getInstance(Config.class).getDefaultFile()); registerSingleton(MyModel.class, model); CustomChart chart = new CustomChart(getInstance(Config.class).getChartOptionOne(), getInstance(Config.class).getChartOptionTwo()); registerSingleton(CustomChart.class, chart); // 注册第一个SubView registerSingleton(SubView.class, new SubView(getInstance(MyModel.class))); // 注册带Chart的SubView,用名称标识 registerNamed("subViewWithChart", new SubView(getInstance(MyModel.class), getInstance(CustomChart.class))); View view = new View(getInstance(SubView.class), (SubView) getNamed("subViewWithChart")); registerSingleton(View.class, view); registerSingleton(MyApp.class, new MyApp(getInstance(MyModel.class), getInstance(View.class))); } // 省略注册、获取实例的工具方法实现... }
main方法里只需要初始化容器并获取实例:
public static void main(String[] args) { SimpleDIContainer container = new SimpleDIContainer(); container.init(); MyApp myApp = container.getInstance(MyApp.class); myApp.start(); }
这个方案适合依赖复杂的场景,能帮你省去手动组装的麻烦,还能轻松实现单例、原型等对象生命周期管理。
方案3:用构建器模式优化对象创建(适合参数多的对象)
如果MyApp或者View这类核心对象的构造参数特别多,用构建器模式能让代码可读性更强,还能灵活组合不同的依赖配置。比如给MyApp写一个构建器:
public class MyAppBuilder { private MyModel myModel; private View view; // 提供设置依赖的方法,链式调用更清爽 public MyAppBuilder withModel(MyModel model) { this.myModel = model; return this; } public MyAppBuilder withView(View view) { this.view = view; return this; } // 提供默认依赖的快捷方法,减少外部代码的复杂度 public MyAppBuilder withDefaultModel() { this.myModel = MyModelFileReader.getModel(Config.getDefaultFile()); return this; } public MyAppBuilder withDefaultView() { if (myModel == null) { throw new IllegalStateException("请先设置Model"); } Config config = Config.getInstance(); CustomChart chart = new CustomChart(config.getChartOptionOne(), config.getChartOptionTwo()); this.view = new View(new SubView(myModel), new SubView(myModel, chart)); return this; } // 构建最终对象,还可以加参数校验 public MyApp build() { if (myModel == null || view == null) { throw new IllegalArgumentException("Model和View不能为空"); } return new MyApp(myModel, view); } }
main方法里的代码就会变得非常直观:
public static void main(String[] args) { MyApp myApp = new MyAppBuilder() .withDefaultModel() .withDefaultView() .build(); myApp.start(); }
这个方案的优势是代码可读性极强,而且如果后续需要自定义某个依赖(比如用测试用的Model),只需要调用withModel(testModel)就行,非常灵活。
总的来说,核心原则就是让main方法只做“启动应用”这一件事,把对象组装的脏活累活交给专门的组件去处理。如果是小项目,工厂模式足够;中大型项目可以考虑DI容器;如果对象参数多,构建器模式会更友好。
内容的提问来源于stack exchange,提问作者user13685906

