依赖注入需要多长时间?

时间:2016-03-11 10:19:58

标签: dependency-injection solid-principles

最近发现了依赖注入,我现在正试图掌握它的使用频率和距离。

例如,假设我有一个对话框,提示用户注册详细信息 - 名字,姓氏,电话号码,序列号等等。应以各种方式验证数据(例如,名字和姓氏不为空,序列号是一定长度)。验证后,它应缓存在本地计算机上,并发送到注册服务器。只有在所有这些事情都成功或用户取消后,对话框才会关闭。

这可能是我们在这里尝试实现的四件事(责任):UI,验证,本地缓存,将数据发送到非本地服务器。

对话框的责任是什么以及应该注入什么?显然,对话框会执行UI,但是应该注入验证,缓存和数据发送吗?我认为他们这样做,否则对话类必须知道数据字段背后的逻辑才能进行验证,它必须知道如何以及在何处缓存数据,以及如何在某处发送数据。如果是这样,这可能导致调用者端的一些重量代码(假设我们通过构造函数进行注入,我认为优于setter函数),例如。

MyDialog dlg(new validator(), new cacher(), new sender());

但也许那没关系?经过多年看到代码之类的事情,像对话做的所有事情,现在 看起来对我来说有点陌生。但我也可以看到这种情况如何迅速升级 - 如果还有其他各种小事需要做什么 - 注入了多少东西变得“太多”?

请不要尝试在示例场景中选择漏洞,我只是用它来说明。我对DI的原则更感兴趣,你可能会把它放得太远。

2 个答案:

答案 0 :(得分:1)

嗯,你当然可以做到这一点。注入验证很有意义,因为您可以围绕验证代码编写单元测试,这些代码不需要激活任何GUI组件即可工作。注入缓存是有道理的,因为对话框不必知道有关其接口之外的缓存系统的任何信息。注入一个发送者很有意义,因为你的对话框没有任何关于任何事情的最模糊的想法。

我习惯把事情分得很厉害,因为我喜欢单一责任原则,我喜欢编写尽可能纯粹的代码。

问题是当你注入太大的接口时,你不再有任何合理的想法,你注入的东西中的哪些接口可能实际上需要调用,并且交互变得复杂并且你的单元测试开始依赖于对依赖项所做的精确处理,因为当你知道75%的不使用它时,你不会费心去模拟整个界面。

因此,确实注入明显不同职责的东西,但要确保以适当的约束方式设计其界面。类可以同时实现多个接口,因此不像你不能将接口切成小块,而是根据需要使用相同的对象实现它们。从属代码永远不必知道!

至于当你把它拿得太远...很难说真的,但我不认为你到达那一点,直到你用一个没有添加任何东西的界面注入东西。我总是想注入有副作用的东西,因为这对单元测试和保持事情更合理是一个巨大的帮助。如果你可以将业务逻辑拆分成纯类并注入那些你将会有一个非常棒的时间为它编写单元测试,那么这可能是值得的。

我使用这样的测试:

  1. 它是否进行I / O并且我还没有进入I / O提供类?注入它。
  2. 它是否提供自包含处理,我不需要知道细节?注入它。
  3. 它做了哪些不属于我的责任?注入它。
  4. 您的里程可能会有所不同。

答案 1 :(得分:0)

你偶然发现了很多人在努力解决的一个令人困惑的问题。使用构造函数注入时,自然倾向于将所有顶级服务推送到应用程序的入口点。

反模式称为构造函数过度注入。当一个类有超过3或4个依赖项时,它是代码气味它(在这种情况下,你的表单)可能违反单一责任原则。发生这种情况时,您应该考虑创建组合相关功能的facade services

虽然您的validatorcachersender是单独的服务,但它们的功能显然是相关的。实际上,它们的功能可能与多个方法调用重叠。

例如,在这个特定的实例中,对cachersender使用decorator pattern可能是有意义的,因为在从{{1}读取时您将缓存数据(我会考虑接收器 - 响应/请求),您可能还需要将数据直接写入UI中的sender cacher,以便将数据写入持久存储后,您无需重新加载缓存。

sender

用法

public interface IDataService
{
    IData ReadData(int id);
    void WriteData(IData data);
}

public class Sender: IDataService
{
    public IData ReadData(int id)
    {
        // Get data from persistent store
    }

    public void WriteData(IData data)
    {
        // Write data to persistent store
    }
}

public class Cacher : IDataService
{
    public readonly IDataService innerDataService;
    public readonly ICache cache;

    public Cacher(IDataService innerDataService, ICache cache)
    {
        if (innnerDataService == null)
            throw new ArgumentNullException("innerDataService");
        if (cache == null)
            throw new ArgumentNullException("cache");

        this.innerDataService = innerDataService;
        this.cache = cache;
    }

    public IData ReadData(int id)
    {
        IData data = this.cache.GetItem(id);
        if (data == null)
        {
            data = this.innerDataService.ReadData(id);
            this.cache.SetItem(id, data);
        }
        return data;
    }

    public void WriteData(IData data)
    {
        this.cache.SetItem(id, data);
        this.innerDataService.WriteData(data);
    }
}

根据验证的方式,将验证另外MyDialog dlg = new MyDialog(new validator(), new cacher(new sender())); 也可能有意义。