示例:
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());
}
}
你觉得怎么样?
力学
在机械级别上,使用委托是完全可以的,因为委托基本上是一个匿名的角色接口。换句话说,是否注入委托、接口或抽象基类并不重要。
概念
在概念层面上,重要的是要记住依赖注入的目的。您使用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>
委托:
- 当采用
Func<T>
构造函数参数的类位于Composition Root中时,因为在这种情况下,它是容器配置的一部分,而我不认为这是应用程序设计的一部分 - 当
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可能会更好。我是对的。
抱歉我的英语不好