4

我有以下具有几个依赖项的基类:

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 容器中注册为单例?

4

9 回答 9

10

正如您所说,您应该避免传递容器。这把它变成了一个“持有的袋子”,你不再能看到你的依赖是什么,你也不能轻易地看到袋子里有什么。

相反,如果你发现你的构造函数接受了太多参数,这本身就是一种味道。在这种情况下,你会经常发现你的类试图做太多的事情(它违反了单一职责原则)。

查看您的参数列表,看看您是否可以将参数分组为更小的组。例如,如果您的构造函数采用IEmailSender,IEventLogILoggingService,那么您真正需要的是INotificationService聚合这三个依赖项的一个。

当然,有时您确实有一个具有这么多依赖项的构造函数。在这种情况下,该类可能只是用于将这些东西收集在一起并将它们连接起来。如果是这种情况,班级可能应该避免做任何实际工作。

于 2013-01-29T11:18:53.657 回答
7

在构造函数中传递所有依赖项是最干净的方法。

我没有看到在派生类中传递参数的问题。避免打字是错误的动机,并且有像 Resharper 这样的工具可以帮助您生成这些构造函数。

如果您有很多依赖关系,则表明该类违反了单一责任模式。

在许多情况下,支持组合而不是继承也是一个好主意。这也有助于将类拆分为不违反 SRP 的较小部分。

于 2013-01-29T12:09:38.287 回答
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() {
        // ...
    }
}
于 2013-01-29T10:53:45.863 回答
1

如果您希望在一系列类中保持一致的行为,AOP可能是一个解决方案。这是使用 PostSharp 进行日志记录的示例:http: //www.sharpcrafters.com/solutions/logging

但是,对于临时日志记录,这可能并不完全适合您的需求。

于 2013-01-29T11:12:29.177 回答
1

当您有许多级别的继承时,这可能会很麻烦,但是明确告诉类具有哪些依赖项(通过构造函数)是一件好事。另一种方法是Annotating Objects for Property (Setter) Injection,但我建议您仅将其用于可选依赖项,即记录器。

于 2013-01-29T11:13:27.120 回答
1

避免传递容器。这是服务地点。您必须反转控制,以便创建 ViewModel 的任何内容都为您提供相关的日志记录服务。

马克·西曼(Mark Seeman)在他的装饰书中很好地做到了这一点。正如有人已经强调的那样,AOP 是一个整洁的替代方案。

你的代码应该变成:

public ViewModel(ILoggingService logger)
{
    loggingService= logger;
}

public ILoggingService LoggingService
{
    get
    {
        return this.loggingService;
    }
}
于 2013-01-29T11:21:11.643 回答
1

CommonServiceLocator可以为您提供一种通过静态调用解析资源的方法。

Unity docs Using Injection Attributes显示了在构造函数注入不可行的情况下可供选择的其他方法。

当我有一个公共基类时,我喜欢使用属性设置器注入进行日志记录:

abstract class Widget
{
   [Dependency]
   public ILogger { set; set; } // Set it and forget it!
}

我不能说我曾经真正使用过方法调用注入。

您不应该仅仅因为您不能设计所有东西以使所有依赖项始终通过构造函数进入...在一个完美的世界中,您不应该觉得自己做错了什么,但在实践中,有选择是很好的.. .

于 2013-01-29T11:24:57.853 回答
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 ...
}
于 2014-06-13T09:01:18.127 回答
-2

你应该坚持你的第一个模式。如果您厌倦了添加这些构造函数变量,那么您有太多了。考虑把你的班级分成更小的部分。这种模式非常强大,因为它可以通过懒惰进行自我调节:)

如果你有一个全局类型依赖,你可能想在任何地方使用(日志是一个完美的例子)......只需使用单例模式......(注意我还假设容器也是使用单例模式创建的)。

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

这样你就不必将它注入到所有东西中。

您通常应该避免这种模式,除非它在很多模块中使用......

于 2013-01-29T10:59:58.207 回答