【问题标题】:Is Func<in T, out TResult> appropriate to use as a ctor arg when applying Dependency Injection?Func<in T, out TResult> 在应用依赖注入时是否适合用作 ctor arg?
【发布时间】:2011-11-27 13:25:35
【问题描述】:

例子:

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());
    }
}

你怎么看?

【问题讨论】:

    标签: c# dependency-injection delegates inversion-of-control func


    【解决方案1】:

    力学

    在机械层面上,使用委托非常好,因为委托基本上是anonymous Role Interface。换句话说,注入委托、接口还是抽象基类并不重要。

    概念

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

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

    作为依赖的委托

    首先关心的是代表传达意图的能力。有时,接口名称本身就传达了意图,而标准委托类型几乎不这样做。

    例如,type 本身并不能传达太多的意图:

    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)
    

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

    public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)
    

    换句话说,使用委托将焦点从类型名称转移到参数名称。这不一定是问题 - 我只是指出您需要注意这种转变。

    可发现性与可组合性

    当涉及到实现依赖关系的类型时,另一个问题是可发现性与可组合性。例如,这两个都实现了 public Func&lt;Type, IBusinessTransaction&gt;

    t => new MyBusinessTransaction()
    

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

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

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

    请注意,这不仅适用于程序员阅读代码,也适用于 DI 容器。因此,当您使用接口时,更容易实现约定优于配置

    1:1 接口与 RAP

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

    但是,many 1:1 interfaces are really a symptom of a violation of the Reused Abstractions Principle。当您重构代码库以在多个地方重用相同的抽象时,将抽象的角色显式建模为接口(或抽象基类)是有意义的。

    结论

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

    【讨论】:

    • 我喜欢使用委托作为依赖。一方面,它使单元测试更加简单。一个有趣的副作用是您可以根据委托定义 API,并避免实际编写任何代码——例如,只需查看 OWIN 规范。一旦你开始注入委托,代码就会开始看起来更有功能。不久前,我写了一篇关于“柯里化容器”如何允许人们完全使用委托 (mikehadlow.blogspot.com/2010/03/…) 编写 C# 代码的文章。
    • 就可读性而言,您可以简单地定义自己的委托,而不是使用内置的 Func 和 Action。这也可以解决可发现性和重复签名问题。
    • 定义自定义委托对可发现性有何帮助?即使使用自定义委托类型,我也看不到任何简单方法来扫描程序集以查找匹配方法。
    【解决方案2】:

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

    但是,有两种情况我不介意注入 Func&lt;T&gt; 代表:

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

    【讨论】:

      【解决方案3】:

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

      使用接口时,模拟依赖项所需的代码更易于阅读,并且您还可以利用自动类型工厂的优势,正如@devdigital 提到的那样。

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

      【讨论】:

        【解决方案4】:

        @Steven 的回答经过深思熟虑;我个人发现Func&lt;T&gt; 参数的使用有限。

        我不明白的是BusinessTransactionFactory&lt;T&gt;IBusinessTransactionFactory&lt;T&gt; 的值。这些只是代表的包装。大概你在其他地方注入IBusinessTransactionFactory&lt;T&gt;。为什么不直接在那里注入委托?

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

        【讨论】:

        • 你是绝对正确的。我已经删除了接口:) 拥有 BusinessTransactionFactory 更具可读性。此类在 UI 中使用。我不希望你有很多 func 的构造函数,而是只有一个类有 func。我会更新我的问题。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

            如果你注入 Func,我认为代码会很懒,所以在某些情况下,如果你的类依赖于许多其他依赖项,那么在这种情况下注入 Funct 可能会更好。我是对的。

            对不起我的英语不好

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2016-05-30
              • 1970-01-01
              • 2018-08-23
              • 1970-01-01
              • 1970-01-01
              • 2011-04-14
              • 1970-01-01
              相关资源
              最近更新 更多