使用手动依赖注入构建多级对象树

时间:2015-12-18 14:46:20

标签: java unit-testing design-patterns dependency-injection

我正在努力重构我编写的Java应用程序。在撰写原文时,我做了一些糟糕的设计决定,现在正在承受后果。特别是,代码几乎不可能进行单元测试,并且依赖性已经到处都是。

我一直在阅读有关设计模式和良好OO设计原则(包括SOLID)的大量内容。我认为解决我的问题的关键在于:构造函数依赖注入(理想情况下不是通过像Spring这样的框架;我宁愿坚持普通的旧Java),工厂,构建器和一般的OO设计。

没有太多细节,想象一下我有一个对象结构,其中O1有一个或多个O2子对象,每个O2对象有一个或多个O3子对象,依此类推。因此,基本上,O1是O2的父级,O2是O3的父级。 (但不是继承意义上的。)

我想要表示的依赖项属于父级的子级。因此,例如,将依赖项注入构造函数可能会导致类似这样的内容:

// highest-level object; no dependencies
O1 o1a = new O1();

// second-level objects; depend on highest-level object
O2 o2a = new O2(o1a);
O2 o2b = new O2(o1a);

// 3rd-level objects; depend on second-level objects
O3 o3a = new O3(o2a); // dependency on o2a
O3 o3b = new O3(o2a); // dependency on o2a
O3 o3c = new O3(o2b); // dependency on o2b

好的,所以这本身很简单。但是有一些复杂的因素:

  • 这些对象中的每一个都可能构造起来非常复杂(一些涉及解析模式以及构建与对象关联的内存中对象模型);
  • 我在编译时不知道每个对象需要多少(这取决于配置);因此,手动编码依赖关系(如上所述)不是一种选择;
  • 我不想"新"特定的类,因为我希望代码是可扩展的,并希望能够" stub"用于测试的不同版本; (工厂);
  • 我需要能够在最低级别开始创建过程&#34 ;;例如,我可能在运行时知道我需要创建一个特定的O3对象;要做到这一点,需要确定O3"父母" (O2),然后是O2的父级(O1),然后创建O1,创建O2(注入新创建的O1),创建O3(注入新创建的O2)等。
  • 我也可能想要跟踪对象,以便可以重复使用它们;所以,在上面的例子中,o3a和o3b都使用了o2a;如果o2a是在创建o3a的过程中创建的,我想在创建o3b时重用相同的对象。

在我所有的设计模式阅读中,遗憾的是,我从未见过如何像这样模糊地完成某些事情的例子。 (通过比较,所有例子都非常简单。)

我想知道为O3创建某种工厂或构建器,并为O2传递一个工厂或构建器的实例。反过来,O2工厂或构建器的实例将使用O1的工厂或构建器的实例创建。但我不确定这是否是正确的方法,即使是这样,我也很难超越高级概念。

我非常感谢有关如何以干净,可维护,可扩展的方式完成所有这些工作的一些指导。我知道,这是一个很高的订单。

提前致谢,
格雷格

1 个答案:

答案 0 :(得分:2)

我开始时遇到的问题基本上和你一样。我想要一个纯Java"注射"没有魔法的解决方案,所以我创建了JayWire。即使您不打算使用此库,也可以查看Wiki的一些想法。

具体来说,基本思想是注入工厂(java.util.function.Supplier)而不是构造对象。这样,消费类不必每次都知道对象是单例,汇集还是构造(新的)。它是这样的:

public class O3 {
    private Supplier<O2> o2Supplier;

    public O3(Supplier<O2> o2Supplier) {
        this.o2Supplier = o2Supplier;
    }

    public void doSomething() {
        ...
        // Here O3 wants an O2 instance, but does not
        // need to know how to construct it, or even
        // whether its constructed, or the same instance
        // is re-used.
        O2 o2 = o2Supplier.get();
        o2.doIt(...);
        ...
    }
}

这种方式是隐藏&#34;隐藏&#34;来自O3,它只知道它需要O2,但并不一定需要知道O2是如何构建的。

您当然可以将供应商更改为工厂(或其他),尽管工厂暗示它将始终创建新实例。

你甚至可能在O2的构造中有一些本地的参数,但仍然想隐藏它需要来自消费者O3的O1这一事实。这也可以这样做:

public class O3 {
    private Supplier2<String, Integer, O2> o2Supplier;

    public O3(Supplier2<String, Integer, O2> o2Supplier) {
        this.o2Supplier = o2Supplier;
    }

    public void doSomething() {
        ...
        // Here O3 wants an O2 instance again, but also
        // wants additional parameters for its construction.
        O2 o2 = o2Supplier.get("Param1", 5);
        o2.doIt(...);
        ...
    }
}

Supplier2与供应商类似,它的get()方法只需要2个额外的参数。

这样,O3仍然不知道O2需要O1,因此可以分解层次结构。您可能希望查看&#34;传播依赖关系&#34;在Wiki中。

JayWire实现了这一切,并且还可以帮助您轻松创建这些供应商。