通过编写DI容器来了解依赖注入-从头开始! (第1部分)

依赖注入(DI)可能是一个非常难掌握的主题,因为似乎有很多“魔术”正在发生。 通常情况下,它涉及到一堆注释,这些注释遍布整个地方,对象看起来似乎无处不在。 我当然知道我花了很长时间才真正理解了这个概念。 如果您发现很难理解Spring和Java EE在幕后所做的事情(以及原因!),请继续阅读!

在本教程中,我们将构建一个非常简单但功能齐全的依赖项注入容器从头开始在Java中。 以下是一些使事情易于管理的规则:

  • 绝对没有图书馆允许,除了JDK本身。没有预先准备的代码转储。 我们将仔细研究所有内容并对此进行推理。No bells & whistles, just the basics.性能无关紧要,没有优化。可执行的主要()方法的每一步。

与其他文章相反,我将不预先解释什么是DI或为什么有用。 相反,我们将从一个简单的示例开始,让它“演变”。

DI Stage 0: Basic example

Find the source code of this section on github

我们从一个非常基本的例子开始。 我们要两个课,我们称它们为服务和服务B,和服务需要服务B做它的工作。 好吧,你可以用静态的 methods和be done with it! So here goes:

public class ServiceA {

    public static String jobA(){
        return "jobA(" + ServiceB.jobB() + ")";
    }

}
public class ServiceB {

    public static String jobB(){
        return "jobB()";
    }

}
public class Main {

    public static void main(String[] args) {
        System.out.println(ServiceA.jobA());
    }

}

如果我们运行此代码,它将打印:

jobA(jobB())

凉! 那么,为什么还要为此烦恼呢? 好...

  • 就OOP原则而言,该代码非常差。 ServiceS和ServiceS至少应对象。The code is tightly coupled and very hard to test in isolation。We have absolutely no chance of swapping neither ServiceA nor ServiceB with a different implementation。 Imagine one of them is doing credit card billings; you 别 want that to actually happen in your test suite。

DI Stage 1: Getting rid of static references

Find the source code of this section on github

我们在阶段0中确定的主要问题是只有静态方法,导致极其紧密的耦合。 我们希望我们的服务成为彼此交谈的对象,以便我们可以根据需要替换它们。 现在出现了一个问题:ServiceA如何知道哪一个服务交谈? 最基本的想法是将Service的实例简单地提供给ServiceA的构造函数:

public class ServiceA {

    private ServiceB serviceB;

    public ServiceA(ServiceB serviceB){
        this.serviceB = serviceB;
    }

    public String jobA(){
        return "jobA(" + this.serviceB.jobB() + ")";
    }

}

服务B的变化不大:

public class ServiceB {

    public String jobB() {
        return "jobB()";
    }

}

...和主要现在需要先组装对象,然后才能调用工作方法:

   public static void main(String[] args) {
        ServiceB serviceB = new ServiceB();
        ServiceA serviceA = new ServiceA(serviceB);

        System.out.println(serviceA.jobA());
    }

这里没什么好看的,对吧? 我们当然改进了一些东西:

  • 我们现在可以替换执行服务B由...使用服务通过提供另一个对象,甚至可能是另一个子类的对象。We can test both services in isolation with a proper test mock for 服务.

那么酷吗? 不完全的:

  • 创建模拟游戏很困难,因为我们需要类。如果我们需要的话会更好介面 instead。Also, this would further reduce the coupling.

DI Stage 2: Using interfaces

Find the source code of this section on github

因此,我们为每个服务使用一个接口。 它们非常简单(我将实际的类重命名为ServiceAImpl和ServiceBImpl, 分别):

public interface ServiceA {

    public String jobA();

}
public interface ServiceB {

    public String jobB();

}

现在,在ServiceAImpl,我们可以实际使用该接口:

public class ServiceAImpl implements ServiceA {

    private final ServiceB serviceB;

    public ServiceAImpl(ServiceB serviceB){
        this.serviceB = serviceB;
    }

    // jobA() is the same as before
}

这也使我们主要()方法好一点:

  public static void main(String[] args) {
        ServiceB serviceB = new ServiceBImpl();
        ServiceA serviceA = new ServiceAImpl(serviceB);
        System.out.println(serviceA.jobA());
    }

从面向对象的角度来看,这对于简单的用例是完全可以接受的。 如果您一定可以解决这个问题,请在此处停止。 但是,随着您的项目变得越来越大...

  • 它会变得越来越复杂建立服务网络在你里面主要()方法。你会遇到周期在您的服务依赖项中,如我们的示例所示,无法使用构造函数来解决。

DI Stage 3: Breaking the cycle with setters

Find the source code of this section on github

让我们假设不仅服务需要服务B,但反之亦然-我们有一个循环。 当然,我们仍然可以在* Impl类,像这样:


 // constructor examples
 public ServiceAImpl(ServiceB serviceB) { ... }
 public ServiceBImpl(ServiceA serviceA) { ... }

...但这对我们没有好处:我们将无法创建实际的实例 of either of the two classes. To create an 实例 of 服务Impl, we would first require an 实例 of 服务B, and to create an 实例 of 服务BImpl we first require an 实例 of 服务。 我们陷入僵局。

旁注:实际上,使用代理。 但是,我们不会在这里走那条路线。

那么我们该怎么办呢? 好吧,由于我们正在处理循环依赖关系,因此我们需要具备以下能力:创建服务,然后线他们在一起。 我们通过setter来做到这一点:

public class ServiceAImpl implements ServiceA {

    private ServiceB serviceB;

    // no constructor anymore here!

    @Override // <- added getter to ServiceA interface
    public ServiceB getServiceB() { return serviceB; }

    @Override // <- added setter to ServiceA interface
    public void setServiceB(final ServiceB serviceB) { this.serviceB = serviceB; }

    // jobA() same as before
}

我们的主要()方法看起来像这样:

    public static void main(String[] args) {
        // create instances
        ServiceB serviceB = new ServiceBImpl();
        ServiceA serviceA = new ServiceAImpl();

        // wire them together
        serviceA.setServiceB(serviceB);
        serviceB.setServiceA(serviceA);

        // call business logic
        System.out.println(serviceA.jobA());
    }

你看到图案了吗? 首先创建对象,然后将它们连接以形成服务图(即服务网络)。

那为什么不停在这里呢?

  • 手动进行布线部分容易出错。 您可能会忘记打电话给二传手,然后空指针异常嘉豪您可能会不小心使用仍在构建中的服务实例,因此以某种方式封装网络构建将是有益的。

Up next

在下一篇博客文章中,我们将讨论如何使目前正在手动执行的布线自动化。

from: https://dev.to//martinhaeusler/understanding-dependency-injection-by-writing-a-di-container-from-scratch-part-1-1hdf

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
DIContainers is a collection of more than 100 individual container classes for Delphi (Embarcadero, CodeGear, Borland). Four different container structures are available: Hash containers Doubly-linked lists N-ary trees Vector containers DIContainers is designed with easy customization in mind. Unlike other libraries, it strictly separates a containers' data and structure: Data describes an individual item in the container, for example an Integer number. The data layer is mostly responsible to manage the item's memory, but also for copying, comparing, and streaming of items. Data items in DIContainers are made up of memory efficient Pascal records. Special item handlers take care to initialize and finalize items automatically as required. Structure describes the arrangement of items within the container, like linked list, linear vector, etc. The structure determines how quickly items can be added, manipulated and retrieved from the container. Using different item handlers, the same structure can provide for quite different containers (see hierarchy on the right). To create a new container for some type of data, it is often sufficient to reuse an already existing item handler or to create a new item handler for the new type of data. Type. On top of the general container classes, there are many ready-made containers which interface typed access to their items like strings (WideStrings and AnsiStrings), different Number types, Objects, Pointer, and various combinations of the above. More than 100 of these containers are ready to use straight out of the box. Advanced container operations include cross-container assignment (i.e. from lists to vectors) and cascading streaming. The graphic to the right shows the class hierarchy of containers contained in DIContainers. Bold font marks important classes like item handlers and structure containers. Their descendant classes in regular font provide typed access to their items. The graphic was automatically created from the DIContainers library by one of the demo applications.
Elite Container是DELPHI下的一个轻量级IoC对象容器(IoC:Inverse of Control,反转控制)。它是参考了Java中的Spring框架(主要是配置文件的写法),并结合DELPHI的特点来构建的。相比Spring的对象容器,它提供的功能更为精简常用(如对象延迟创建、对象属性自动注入等),降低了学习的难度,并且提供了很多扩展点,你只需简单地写一个插件实现类,并在配置文件中进行简单配置,就可以让Elite Container拥有你的自定义功能! 借助Elite Container和Ioc思想,你可以更轻易地构建出具有松散耦合、重用度高的应用程序。它的核心思想就是拆分功能的接口和实现,上层只依赖于下层的接口,然后通过Elite Container的配置,把不同的实现类注入到该接口中,达到更换功能,也就是复用已有代码的目的。设计人员可以真正地发挥好自己的面向对象思想和相关设计模式,来架构企业级的应用程序,而无需象以前那样,在Delphi中用起面向对象总有点捉襟见袖的感觉。 ps. 由于写程序,还有精心准备的26个例子,都花费了我很多的时间,所以象征性地收2个资源分,希望大家不会介意:) ps2. 说明一下,开发出来的程序在发布的时候,除了发布EliteCore.bpl、EliteContainer.bpl外,还需要发布rtl.bpl(EliteCore.bpl引用到了它)。由于一时疏忽,例子中的Bin目录里忘记带上它了,请大家注意,谢谢~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值