是Func<;in T out TResult>;适用于在应用依赖注入时用作ctor参数

本文关键字:注入 应用 依赖 参数 ctor gt in lt Func out TResult | 更新日期: 2023-09-27 18:15:57

示例:

public class BusinessTransactionFactory<T>  where T : IBusinessTransaction
{
    readonly Func<Type, IBusinessTransaction> _createTransaction;
    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTransaction)
    {
        _createTransaction = createTransaction;
    }
    public T Create()
    {
        return (T)_createTransaction(typeof(T));
    }
}

使用相同的集装箱设置代码:

public class DependencyRegistration : Registry
{
    public DependencyRegistration()
    {
        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.WithDefaultConventions();
            x.AddAllTypesOf(typeof(Repository<>));
            x.ConnectImplementationsToTypesClosing(typeof(IRepository<>));
        });
        Scan(x =>
        {
            x.AssembliesFromApplicationBaseDirectory();
            x.AddAllTypesOf<IBusinessTransaction>();
            For(typeof(BusinessTransactionFactory<>)).Use(typeof(BusinessTransactionFactory<>));
            For<Func<Type, IBusinessTransaction>>().Use(type => (IBusinessTransaction)ObjectFactory.GetInstance(type));
        });

        For<ObjectContext>().Use(() => new ManagementEntities());
    }
}

你觉得怎么样?

是Func<;in T out TResult>;适用于在应用依赖注入时用作ctor参数

力学

在机械级别上,使用委托是完全可以的,因为委托基本上是一个匿名的角色接口。换句话说,是否注入委托、接口或抽象基类并不重要。

概念

在概念层面上,重要的是要记住依赖注入的目的。您使用DI的原因可能与我不同,但IMO DI的目的是提高代码库的可维护性

是否通过注入委托而不是接口来实现这一目标值得怀疑。

作为依赖项的委托

第一个问题是代理传达意图的能力。有时,接口名称本身传达意图,而标准委托类型几乎不传达意图。

例如,类型本身并不能在这里传达太多意图:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)

幸运的是,createTranscation名称仍然暗示了委托所扮演的角色,但只需考虑(为了论证起见(如果作者不那么小心,该构造函数的可读性会有多强:

public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)

换句话说,使用delegates将焦点从类型的名称转移到参数的名称。这不一定是个问题——我只是指出,你需要意识到这种转变。

可发现性与可组合性

当涉及到实现依赖关系的类型时,另一个问题是可发现性与可组合性。举个例子,这两者都实现了公共Func<Type, IBusinessTransaction>:

t => new MyBusinessTransaction()

public class MyBusinessTransactionFactory
{
    public IBusinessTransaction Create(Type t)
    {
        return new MyBusinessTransaction();
    }
}

然而,在类的情况下,具体的非虚拟Create方法与所需的委托匹配几乎是偶然的它很容易组合,但不太容易发现

另一方面,当我们使用接口时,类在实现接口时成为is-a关系的一部分,因此通常更容易找到所有实现者并相应地对其进行分组和组合。

请注意,这不仅适用于阅读代码的程序员,也适用于DI容器。因此,当您使用接口时,可以更容易地实现Convention over Configuration

与RAP的1:1接口

有些人注意到,当尝试使用DI时,他们最终会得到很多1:1的接口(例如IFoo和相应的Foo类(。在这些情况下,接口(IFoo(似乎是多余的,并且似乎很想去掉接口,转而使用委托。

然而,许多1:1接口实际上是违反重用抽象原则的症状。当重构代码库以在多个地方重用同一抽象时,将该抽象的角色显式建模为接口(或抽象基类(是有意义的。

结论

界面不仅仅是一种机制。它们在应用程序代码库中显式地为角色建模。中心角色应该由接口表示,而一次性工厂及其同类可以作为委托来使用和实现。

我个人不喜欢将Func<T>参数视为类中的依赖项,因为我认为这会降低代码的可读性,但我知道许多开发人员不同意我的观点。一些容器,如Autofac,甚至允许您解析Func<T>委托(通过返回() => container.Resolve<T>()(。

然而,有两种情况我不介意注入Func<T>委托:

  1. 当采用Func<T>构造函数参数的类位于Composition Root中时,因为在这种情况下,它是容器配置的一部分,而我不认为这是应用程序设计的一部分
  2. Func<T>被注入到应用程序中的单个类型中时。当更多的服务开始依赖于同一个Func<T>时,创建一个特殊的I[SomeType]Factory接口并注入它会更好地提高可读性

在过去,我使用过您提到的模式(委托(和单个方法接口。目前,我倾向于使用单一方法接口。

当使用接口时,模拟依赖关系所需的代码更容易阅读,而且您还可以利用自动键入的工厂,正如@devdigital提到的那样。

另一件需要考虑的事情是,在使用接口时不会有委托调用的开销,这在非常高性能的应用程序中可能是一个有效的考虑因素。

@Steven的回答经过深思熟虑;就我个人而言,我觉得Func<T>参数的有限使用是可读的。

我不明白的是BusinessTransactionFactory<T>IBusinessTransactionFactory<T>的值。这些只是代理的包装。大概你在别的地方注射IBusinessTransactionFactory<T>。为什么不直接在那里注入委托呢?

我认为Func<T>(和Func<T, TResult>等(是工厂。对我来说,你有一个工厂类实现了一个包装工厂委托的工厂接口,这似乎是多余的。

我认为这里真正的问题是依赖类型是否应该被视为合同。

在Func委托的情况下,您声明的是对某个方法结构的依赖,但仅限于此。您无法通过查看此结构来推断可接受输入值的范围(前置条件(、对预期输出的约束(后置条件(或它们的确切含义。例如,null是可接受的返回值吗?如果是,那么应该如何解释呢?

在接口的情况下,双方都有一个约定:使用者明确声明它需要一个特定的接口,实现者明确声明提供它。这超出了结构:接口的文档将讨论前置条件、后置条件和语义。

是的,这是完全可以接受的,尽管如果您使用容器,现在越来越多的容器支持自动类型工厂,这将是更好的选择。

  • 温莎城堡-打字工厂设施
  • Ninject-工厂扩展

如果您注入Func I thin,代码将是惰性的,因此在某些情况下,如果您的类依赖于许多其他依赖项,那么在这种情况下注入Funct可能会更好。我是对的。

抱歉我的英语不好