我有以下基类和几个依赖项:
public abstract class ViewModel
{
private readonly ILoggingService loggingService;
public ViewModel(
ILoggingService loggingService,
...)
{
this.loggingService = loggingService;
...
}
}
在我的派生类中,我不想重复此基类构造函数中的所有参数,所以我这样做了:
public abstract class ViewModel
{
private readonly IUnityContainer container;
private ILoggingService loggingService;
...
public ViewModel(IUnityContainer container)
{
this.container = container;
}
public ILoggingService LoggingService
{
get
{
if (this.loggingService == null)
{
this.loggingService = this.container.Resolve<IUnityContainer>();
}
return this.loggingService;
}
}
...
}
现在我的派生类只需要将一个东西传递给我的基类构造函数。我也有很好的效果,只有在需要时才能解决我的依赖关系。
但是,我已经知道传递一个IOC容器是一个坏主意。什么是最好的替代设计模式,记住传入的许多服务已经作为单身人士注册在我的IOC容器中?
答案 0 :(得分:8)
正如您所述,您应该避免将容器放在周围。这会把它变成一个“持有袋”,在那里你再也看不到你的依赖是什么了,而且你不能轻易看出包里有什么。
相反,如果你发现你的构造函数需要太多参数,那么这本身就是一种气味。在这种情况下,您经常会发现您的班级正在尝试做太多事情(这违反了单一责任原则)。
查看参数列表,看看是否可以将参数分组为更小的组。例如,如果您的构造函数采用IEmailSender
,IEventLog
和ILoggingService
,那么您真正需要的是INotificationService
聚合这三个依赖项。
当然,有时你做有一个带有那么多依赖项的构造函数。在这种情况下,该类可能只是用于将这些东西聚集在一起并将它们连接起来。如果是这种情况,那么班级应该避免做任何实际的工作。
答案 1 :(得分:6)
在构造函数中传递所有依赖项是最简洁的方法。
我没有看到在派生类中传递参数的问题。避免打字是错误的动机,并且有像Resharper这样的工具可以帮助你生成这些构造函数。
如果您有很多依赖项,则表明该类违反了单一责任模式。
在许多情况下,它也很好地考虑使用Composition而不是继承。这也有助于将类拆分为不违反SRP的小块。
答案 2 :(得分:1)
只需通过容器创建派生类,并将它们注入您需要的地方。
错误的示例 - Foo
担心Bar
的依赖关系,因为它需要实现Bar
。
class Foo {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public doSomething() {
Bar bar = new Bar(x);
bar.doSomething();
}
}
class Bar {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public void doSomething() {
// ...
}
}
正确的示例 - Foo
并不关心如何创建Bar
。 Bar
直接从容器中获取依赖项。
class Foo {
SomeDependency x;
Bar bar;
public Bar(SomeDependency x, Bar bar) {
this.x = x;
this.bar = bar;
}
public doSomething() {
bar.doSomething();
}
}
class Bar {
SomeDependency x;
public Bar(SomeDependency x) {
this.x = x;
}
public void doSomething() {
// ...
}
}
答案 3 :(得分:1)
AOP可能是一种解决方案。以下是使用PostSharp进行日志记录的示例:http://www.sharpcrafters.com/solutions/logging
对于临时日志记录,这可能不完全符合您的需求。
答案 4 :(得分:1)
当你有很多级别的继承时,这可能很麻烦,但明确告诉一个类有什么依赖关系(通过构造函数)是一件好事。另一种选择是Annotating Objects for Property (Setter) Injection,但我建议您仅将此用于可选的依赖项,即记录器。
答案 5 :(得分:1)
避免将容器绕过。这是服务地点。您必须反转控制,以便创建ViewModel的任何内容都为您提供依赖的日志记录服务。
Mark Seeman在他的装饰书中做得非常好。 AOP是一个整洁的选择,正如有人已经强调的那样。
您的代码应该成为:
public ViewModel(ILoggingService logger)
{
loggingService= logger;
}
public ILoggingService LoggingService
{
get
{
return this.loggingService;
}
}
答案 6 :(得分:1)
CommonServiceLocator可以为您提供通过静态调用解析资源的方法。
Unity docs Using Injection Attributes显示了构造函数注入不是jiving时可以选择的其他方法。
当我有一个共同的基类时,我喜欢使用Property setter注入进行日志记录:
abstract class Widget
{
[Dependency]
public ILogger { set; set; } // Set it and forget it!
}
我不能说我实际上曾经使用过方法调用注入。
你不应该觉得自己做错了只是因为你无法设计所有的东西以便一直通过构造函数进入所有的依赖...在一个完美的世界里也许,但在实践中它很高兴选项...
答案 7 :(得分:0)
我最后使用工厂模式创建了下面的接口和类,以减少添加到构造函数的参数数量:
public interface IInfrastructureFactory
{
ILoggingService LoggingService { get; }
// ... Other Common Services Omitted ...
}
public class InfrastructureFactory : IInfrastructureFactory
{
private readonly ILoggingService loggingService;
// ... Other Common Services Omitted ...
public InfrastructureFactory(
ILoggingService loggingService,
// ... Other Common Services Omitted ...
)
{
this.loggingService = loggingService;
// ... Other Common Services Omitted ...
}
public ILoggingService LoggingService
{
get { return this.loggingService; }
}
// ... Other Common Services Omitted ...
}
在我的IOC容器中,我注册了一次IInfrastructureFactory。在我的视图模型中,我只有一个依赖项,创建一个新的视图模型更加快速和简单。
public abstract class ViewModel
{
private readonly IInfrastructureFactory infrastructureFactory;
public ViewModel(IInfrastructureFactory infrastructureFactory)
{
this.infrastructureFactory = infrastructureFactory;
}
public ILoggingService LoggingService
{
get { return this.infrastructureFactory.LoggingService; }
}
// ... Other Common Services Omitted ...
}
答案 8 :(得分:-2)
你应该坚持你的第一个模式。如果你厌倦了添加那些构造函数变量,那么你就有太多了。考虑将你的课分成更小的一部分。这种模式是如此强大,因为它通过懒惰自我调节:)
如果您有一个全局类型依赖项,您可能希望在任何地方使用它(Logging是一个完美的示例)...只使用单例模式...(注意我还假设容器也是使用单例模式创建的)。
public static LoggingService
{
private static ILoggingService _current;
public static ILoggingService Current
{
get
{
if(_current == null) { _current = Container.Current.Resolve<ILoggingService>(); }
return _current;
}
}
}
然后像......一样使用它。
LoggingService.Current.Log(...);
这样你就不必把它注入所有东西。
除非在很多模块中使用,否则通常应避免使用此模式......