.net 传递IOC容器的替代方法

fjaof16o  于 2023-02-14  发布在  .NET
关注(0)|答案(9)|浏览(124)

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

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容器中注册的?

jdzmm42g

jdzmm42g1#

正如你所说的,你应该避免传递容器,这会把它变成一个“收纳袋”,在那里你不再能看到你的依赖项是什么,也不容易看到袋子里有什么。
相反,如果你发现你的构造函数带了太多的参数,这本身就是一种气味,在这种情况下,你经常会发现你的类试图做太多的事情(这违反了单一责任原则)。
看看你的参数列表,看看你是否可以把参数分成更小的组,例如,如果你的构造函数接受IEmailSenderIEventLogILoggingService,也许你真正需要的是一个聚合这三个依赖项的INotificationService
当然,有时候你的构造函数确实有那么多的依赖项。在这种情况下,类可能只是用来把这些东西聚集在一起并把它们连接起来。如果是这样的话,类可能应该避免做任何实际的工作。

yizd12fk

yizd12fk2#

在构造函数中传递所有依赖项是最干净的方法。
我看不出在派生类中传递参数有什么问题。避免类型化是错误的动机,有像Resharper这样的工具可以帮助你生成这些构造函数。
如果有很多依赖项,则表明该类违反了单一责任模式。
在很多情况下,支持组合而不是继承也是一个好主意,这也有助于将类拆分成更小的部分,而不会违反SRP。

qni6mghb

qni6mghb3#

如果你想在一系列类之间保持一致的行为,AOP可能是一个解决方案。http://www.sharpcrafters.com/solutions/logging
但是,对于临时日志记录,这可能并不完全适合您的需要。

axkjgtzd

axkjgtzd4#

当你有很多层次的继承时,这可能是一个麻烦,但是显式地告诉类有什么依赖项是一件好事(通过构造函数)。一个替代方法是Annotating Objects for Property (Setter) Injection,但是我建议你只对可选的依赖项使用它,例如logger。

qzwqbdag

qzwqbdag5#

避免传递容器。这是服务位置。你必须反转控制,这样无论什么创建你的ViewModel,都会给你依赖的日志服务。
Mark Seeman在他的书中很好地做到了这一点。AOP是一个整洁的替代方案,正如有人已经强调的那样。
您的代码应变为:

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

public ILoggingService LoggingService
{
    get
    {
        return this.loggingService;
    }
}
x759pob2

x759pob26#

CommonServiceLocator可以为您提供一种通过静态调用解析资源的方法。
Unity docs Using Injection Attributes展示了构造函数注入不灵活时的其他方法。

当我有一个公共基类时,我喜欢使用Property setter注入进行日志记录:

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

我不能说我曾经实际使用过方法调用注入。
你不应该仅仅因为你不能设计所有的东西都让所有的依赖项总是通过构造函数进来就觉得自己做错了什么......在一个完美的世界里也许是这样,但在实践中有选择是很好的......

oprakyz7

oprakyz77#

只需通过容器创建派生类,并在需要的地方注入它们。

错误示例-Foo担心Bar的依赖性,因为它需要示例化Bar

class Foo {
    SomeDependency x;
    public Foo(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不关心如何创建BarBar直接从容器获取依赖项。

class Foo {
    public Foo(Bar bar) {
        this.bar = bar;
    }
    public doSomething() {
        this.bar.doSomething();
    }

}

class Bar {
    SomeDependency x;
    public Bar(SomeDependency x) {
        this.x = x;
    }
    public void doSomething() {
        // ...
    }
}
yhqotfr8

yhqotfr88#

最后,我使用工厂模式创建了下面的接口和类,以减少添加到构造函数的参数数量:

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容器中,我注册了一次IInfrastructure工厂,在我的视图模型中,我只有一个依赖项,创建一个新的视图模型要快得多,也简单得多。

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 ...
}
vlurs2pr

vlurs2pr9#

你应该坚持你的第一个模式。如果你厌倦了添加那些构造函数变量,那么你已经有太多了。考虑一下把你的类分解成更小的部分。这个模式是如此强大,因为它可以通过懒惰来自我调节:)
如果你有一个全局类型依赖,你可能想在任何地方使用(日志记录是一个完美的例子)......只需使用单例模式......(注意我还假设容器也是使用单例模式创建的)。

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

这样你就不用把它注射到所有东西里了。
通常应该避免使用这种模式,除非在很多模块中使用它...

相关问题