示例:
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());
}
}
您怎么看?
答案 0 :(得分:25)
<强>力学强>
在机械层面上,使用代表是完全没问题的,因为代表基本上是anonymous Role Interface。换句话说,无论是注入委托还是接口或抽象基类都没关系。
<强>概念强>
在概念层面,重要的是要记住依赖注入的目的。您可能因为与我不同的原因使用DI,但IMO的目的是提高代码库的可维护性。
这个目标是否通过注入代理而不是接口来实现是值得怀疑的。
委托作为依赖项
第一个问题是代表传达意图的程度。有时,接口名称本身就会传达意图,而标准委托类型则很难。
例如, type 本身并没有太多意图:
public BusinessTransactionFactory(Func<Type, IBusinessTransaction> createTranscation)
幸运的是,createTranscation
名称仍然暗示了委托人所扮演的角色,但只是考虑(为了论据的缘故)如果作者不那么小心,构造函数的可读性如何:
public BusinessTransactionFactory(Func<Type, IBusinessTransaction> func)
换句话说,使用委托将焦点从类型名称转移到参数名称。这不一定是个问题 - 我只是指出你需要意识到这种转变。
可发现性与可组合性
另一个问题是关于实现依赖关系的类型的可发现性与可组合性。作为示例,这两个都实现了公共Func<Type, IBusinessTransaction>
:
t => new MyBusinessTransaction()
和
public class MyBusinessTransactionFactory
{
public IBusinessTransaction Create(Type t)
{
return new MyBusinessTransaction();
}
}
但是,在类的情况下,具体的非虚拟Create
方法与所需的委托匹配几乎是偶然的。 它非常易于组合,但不是很容易被发现。
另一方面,当我们使用接口时,类在实现接口时会成为 is-a 关系的一部分,因此通常会更容易找到所有实现者和组并相应地组合它们。 / p>
请注意,这不仅适用于程序员阅读代码,也适用于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。当您重构代码库以在多个位置重用相同的抽象时,将该抽象的角色显式建模为接口(或抽象基类)是有意义的。
<强>结论强>
接口不仅仅是机制。它们在应用程序代码库中明确地模拟角色。中心角色应该由接口表示,而一次性工厂及其同类可以作为代表使用和实现。
答案 1 :(得分:5)
我个人不喜欢在我的类中看到Func<T>
参数作为依赖项,因为我认为它使代码的可读性降低,但我知道许多开发人员不同意我的看法。某些容器(例如Autofac)甚至允许您解析Func<T>
个委托(通过返回() => container.Resolve<T>()
)。
但是有两种情况我不介意注入Func<T>
代表:
Func<T>
构造函数参数的类位于Composition Root时,因为在这种情况下它是容器配置的一部分,我不认为应用程序的那部分设计Func<T>
时。当更多服务开始依赖于相同的Func<T>
时,可读性最好是创建一个特殊的I[SomeType]Factory
接口并注入该接口。答案 2 :(得分:4)
过去,我使用了你提到的模式(委托)和单个方法接口。目前,我倾向于使用单一方法接口。
使用界面时,模拟依赖项所需的代码更容易阅读,并且您还可以利用自动键入的工厂,如@devdigital所述。
另一件需要考虑的事情是,在使用接口时,您不会有代理调用的开销,这在高性能应用程序中可能是一个有效的考虑因素。
答案 3 :(得分:3)
@史蒂文的回答经过深思熟虑;就个人而言,我发现Func<T>
参数的使用有限。
我不明白的是BusinessTransactionFactory<T>
和IBusinessTransactionFactory<T>
的价值。这些只是委托人的包装。据推测,你正在其他地方注射IBusinessTransactionFactory<T>
。为什么不在那里注入代表?
我认为Func<T>
(和Func<T, TResult>
等)是工厂。对我来说,你有一个工厂类实现一个包装工厂委托的工厂接口,这似乎是多余的。
答案 4 :(得分:2)
我认为这里真正的问题是依赖类型是否应该被视为合同。
对于Func委托,您声明对某个方法结构的依赖,但仅此而已。您不能通过查看此结构来推断接受的输入值的范围(前置条件),对预期输出的约束(后置条件)或它们的确切含义。例如,null是否为可接受的返回值?如果是,那该怎么解释?
对于接口,您有双方同意的合同:消费者明确声明它需要特定的接口,并且实现者明确声明提供它。这超出了结构:接口的文档将讨论前置条件,后置条件和语义。
答案 5 :(得分:1)
是的,这是完全可以接受的,尽管如果你使用的是容器,现在越来越多的容器支持自动打字的工厂,这是更好的选择。
答案 6 :(得分:0)
如果你注入Func我瘦了代码将是懒惰的,所以在某些情况下,如果你的类依赖于许多其他依赖项,那么在这种情况下注入Funct可能更好。我是对的。
抱歉我的英文不好