gpt4 book ai didi

c# - 努力进行手动依赖注入(inject)

转载 作者:行者123 更新时间:2023-11-30 16:16:27 25 4
gpt4 key购买 nike

我正在使用C#中的小型(ish)支持库来开发一个项目(可能还有类似的项目)。我已经阅读了很多有关构造函数注入依赖项的知识,虽然我理解了其背后的基本动机,但在处理某些类型的依赖项时这是很有意义的,但我经常遇到这样的情况:它似乎使事情变得更加困难而没有提供很多好处。

我想展示两个示例,非常感谢您提供有关如何“正确”处理它们的任何建议。我还要在这一点上指出,目前我不希望为此使用任何DI容器,因为我希望该库在没有容器的情况下可以使用...

示例1:动态创建的对象

大多数与DI相关的文章都强调,对象图的创建发生在合成的根部。但是,并非所有对象都可以最初创建。对此的标准答案是将工厂用于短期对象。当然可以,但是在某些情况下可能会感到很尴尬...

假设我想在第三方库中的对象周围创建一系列外观类,其中一个对象可能返回另一种类型的对象,并且我想将两者都包装起来。作为一个(组成的)示例,让我们采用一个具有方法“ GetFiles()”的“ Directory”类,该方法返回“ File”类的实例。外墙的非DI版本可能如下所示:

public DirectoryFacade(string path)
{
m_directory = new Directory(path);
}
public IEnumerable<FileFacade> GetFiles()
{
foreach(File file in m_directory.GetFiles())
{
yield return new FileFacade(file);
}
}


相当简单和直截了当。现在使用DI,DirectoryFacade本身不应创建任何FileFacades(也不应创建包装的Directory,但这是较小的问题...)。同样,通常建议的解决方案是建立工厂,例如可以创建我所有包装对象的“ FacadeFactory”类。但这对我来说很尴尬,原因有两个:


现在,所有DirectoryFacade实例都需要传递一个FacadeFactory,以便它们可以动态创建FileFacades。这有点不对劲...
对于图书馆的用户来说,不得不使用工厂来创建DirectoryFacades而不是仅仅能够直接实例化它们可能是不直观的。


现在,可能可以使用默认构造函数或静态工厂方法来提供默认实现,但是这些似乎被规避了,通常是出于对DI容器的关注……而且,在各处使用静态工厂方法也有点不直观在我看来。

示例2:内部帮助程序类

我最近发现的另一个问题是,您将复杂类的实现分解为多个子组件,以遵循单一职责原则,但是您的类用户不必知道...

例如,选择一个类,该类有助于与键/值对的复杂配置字符串进行交互,并提供“ GetValueByKey()”和“ SetValueForKey()”等方法。

在通常的用例中,客户端代码可能只想执行以下操作:

Configuration config = new Configuration(configString);
config.SetValueForKey('foo','bar');
...


在内部,“ Configuration”类可能会将字符串解析为某种数据结构。为此,它可以使用一个名为“ ConfigurationParser”的类。更进一步:解析器本身可以输出IKeyword对象流。 IKeyword接口的具体实现取决于各个关键字的类型。因此,关键字可能是由'KeywordFactory'生成的...所有这些对于'Configuration'类的客户端绝对不感兴趣。实际上,我应该能够在不影响客户端代码的情况下将所有这些更改为其他内容。但是,使用DI时,您需要执行以下操作:

KeywordFactory factory = new KeywordFactory();
ConfigurationParser parser = new ConfigurationParser(factory);
Configuration config = new Configuration(parser);
config.ConfigurationString = configString; //NOTE: this could be a constructor param...
config.SetValueForKey('foo','bar');


现在,从客户的角度来看,这太可怕了。如前所述,如果使用解析器或关键字工厂的通常内部实现详细信息发生更改,也将中断。

当然,可以再次编写一些其他工厂和外观来为客户端提供默认实现,但这真的有意义吗?它提供什么好处?最有可能的是解析器和关键字工厂只有一种实现。此外,它们将仅在“配置”类中使用。他们处于不同班级的唯一原因是每个班级都有明确定义的责任...

最佳答案

现在使用DI,DirectoryFacade本身不应创建任何FileFacades
  


这是您出问题的地方。防止在应用程序中的任何位置使用new关键字绝不是DI的意图。 DI是关于松散耦合的。而且,您不应该尝试抽象那些不必抽象的事物。您应该区分服务(应用程序逻辑)和其他任何东西(DTO,消息,实体,命令,异常,原语等)。这是newables and injectables之间的重要区别。

在这种情况下,您的FileFacades是可更新的。它们是短命的,仅包含数据,没有自身的依赖关系,几乎没有逻辑可以测试,并且主要作为实际实现中的瘦代理存在。注入它们意味着您对将它们替换为其他实现(例如用于测试)感兴趣。但是,如果不是这种情况,请不要打扰他们。在这种情况下,让DirectoryFacade创建FileFacade实例似乎是完全合理的。这完全消除了使用FileFacadeFactory的需要。


  
    因此,关键字可能是由“ KeywordFactory”生成的...
  


这种说法再次来自同样的误解。关键字可能只是数据包。这里没有抽象的逻辑,也没有理由解释为什么ConfigurationParser不应重新创建Keyword实例本身。


  
    可以再写一些其他的工厂和门面来
    提供客户端的默认实现,但是这样做
    真的有意义吗?它提供什么好处?
  


从另一侧看。如果您不像这样构造代码,那么如何对代码进行单元测试?

正如我所看到的,作为框架编写者,您有两个责任。 1.编写正确运行的代码(通过对它进行彻底的单元测试)。 2.设计一个易于客户使用的API。

如果所有这些类都是实现细节,并且不打算让客户端更改,请确保它们保留实现细节。例如,将这些类设置为内部类,并对库的根类型使用穷人的依赖注入。对于穷人的DI,您有一个公共默认构造函数,该构造函数调用另一个接受所有依赖关系的构造函数。这是一个例子:

public class MyPublicAPI
{
public MyPublicAPI()
: this(new Configuration(
new ConfigurationParser()))
{
}

internal MyPublicAPI(IConfiguration configuration)
{
this.configuration = configuration;
}
}


穷人的DI受到许多人的反对,但通常在“业务范围”应用程序的上下文中给出此建议。如果您编写了一个可重用的库,那么拥有一个干净的API是最重要的,并且在运行一个通用用例时,您的用户的确不应被实现一个以上的类困扰(当然,如果用户想做更复杂的事情) ,他们可能需要创建更多的类)。此外,您的用户可能不使用DI容器,或者即使他们使用了DI容器,也不应强迫他们必须注册所有库类型。那不是很友好且容易出错,因为每次添加新类型时,您都会破坏其DI配置。

穷人的DI并不总是一个选择,因为您对所使用的依赖项进行了硬编码。如果要给用户更多的灵活性,您可能仍然不得不依靠创建用户可以覆盖的工厂。这完全取决于您的框架有多复杂,以及需要给用户的灵活性级别。

另一个选择是将集成DLL与您的库一起提供,以将您的库与常见的DI容器集成。因此,请经常查看此内容,但除非用户确实需要这种灵活性,否则不要这样做。您仍在打开库,这有一些缺点。首先,使用户更难以使用框架,并且使API变得更大。这也使您更难更改框架。更改库的公共API可能会导致重大更改。

关于c# - 努力进行手动依赖注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18309265/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com