【问题标题】:Polymorphic Abstract class with a lot of DI paramters具有大量 HDI 参数的多态抽象类
【发布时间】:2018-04-18 18:02:14
【问题描述】:

我经常遇到一个问题,即拥有一个可以完成所有繁重工作的抽象类,然后我有很多多态类可以根据特定需求自定义抽象。抽象一般需要很多参数,所以都得从所有多态类传过来

public class FooComplex : AbstractFoo {
    public FooComplex(IBarAwesome awesome, IBarCool cool, ...) : base(IBarAwesome awesome, IBarCool cool, ...) { }
    ...a lot of overriding abstracts
}

public class FooSimple : AbstractFoo
{
    public FooSimple(IBarAwesome awesome, IBarCool cool, ...) : base(IBarAwesome awesome, IBarCool cool, ...) { }
    ...little bit of overriding abstracts
}

public class AbstractFoo
{
    public AbstractFoo(IBarAwesome awesome, IBarCool cool, ...)
    ...heavy lifting
}

我能做些什么来不通过所有这些东西,但能够对它们进行单元测试?我一直被教导这样做

var awesome = container.Resolve<IBarAwesome>();

就像说构造函数是不好的做法。

我想找到解决方案的原因是,将任何新的东西传递给抽象类变得越来越困难,因为我必须将相同的参数复制并传递给许多多态子类。

【问题讨论】:

  • 如果您希望这些参数随时间而变化,一种解决方案是将它们放在AbstractFooParameters 类中,具体类作为单个参数传递。不知道那是不是你要找的。​​span>
  • 那可以和 DI 一起使用吗?
  • 我不知道 autofac 但它应该与构造函数注入一起使用,就像任何其他构造函数参数一样。
  • 我真的很喜欢这个解决方案,你能把它作为答案发布吗?
  • @IanOverton,我同意 C.Evenhuis 解决方案是我对如何解决此问题的最初想法,但是实施很大程度上取决于这些参数的上下文以及您要实现的目标

标签: c# unit-testing dependency-injection autofac


【解决方案1】:

我相信这类似于 cmets 中提到的 @C.Evenhuis,将您的构造函数参数抽象到一个通用接口中,这样它们就可以作为单个构造函数参数传递并且易于测试。

具体类:

public class FooComplex : AbstractFoo
{
    public FooComplex(ComplexParam complexParam) : base(complexParam)
    {}
}

public class FooSimple : AbstractFoo
{
    public FooSimple(SimpleParam simpleParam) : base(simpleParam)
    {}
}

单个通用具体类(可选)

使用这个类,您可以将任何类型传递给继承 IParams 的构造函数,并可能不再需要 FooComplexFooSimple

public class Foo<T> : AbstractFoo where T : IParam
{
    public Foo(T param) : base(param)
    { }
}

基础抽象类:

public abstract class AbstractFoo
{
 protected AbstractFoo(IParam parameter) { }
}

接口:

public interface IBarCool : IBar
{}

public interface IBarAwesome : IBar
{}

public interface IBar
{}

public interface IParam
{
    IEnumerable<IBar> Param { get; }
}

可重复使用的具体参数:

由于重复,我个人不喜欢下面的这种方法,但我想如果每个类都有自己的单独实现,那没关系。另一种选择是只拥有一个名为ParameterHolder 的类和适当命名的类的两个实例,例如var complex = new ParameterHolder() 并传递给通用 Foo&lt;T&gt;

public class ComplexParam : IParam
{
    public IEnumerable<IBar> Param { get; }

    public ComplexParam(IEnumerable<IBar> complexParam)
    {
        Param = complexParam;
    }
}

public class SimpleParam : IParam
{
    public IEnumerable<IBar> Param { get; }

    public SimpleParam(IEnumerable<IBar> simpleParam)
    {
        Param = simpleParam;
    }
}

【讨论】:

  • 从“用 Autofac 解决这个问题”的角度来看,Autofac supports the "aggregate service" pattern,允许您定义一个接口,其中所需的参数是接口上的属性。 Autofac 将动态生成接口的具体实现并为您填充属性。 See the docs for an example.
  • @TravisIllig 没有使用 Autofac 的乐趣。现在只是通过文档...非常令人印象深刻。
  • 两个类中的参数列表相同,而不是动态列表。只是班级的细节不同。特拉维斯说的很有趣,我得试试。它会成功的,所以我不需要一个具体的课程来完成我想做的事情。
  • +1 尽管我不需要这种解决方案来解决这个问题,但它绝对是一个很棒的解决方案,我必须找到使用它的地方。
【解决方案2】:

所有需要发生的是:

public interface IAbstractParams
{
   IBarAwesome awesome { get; } 
   IBarCool cool { get; }
   ... 
}

public class FooComplex : AbstractFoo 
{
    public FooComplex(IAbstractParams params) : base(params) { }
    ...a lot of overriding abstracts
}

public class FooSimple : AbstractFoo
{
    public FooSimple(IAbstractParams params) : base(params) { }
    ...little bit of overriding abstracts
}

public class AbstractFoo
{
    protected readonly IBarAwesome _awesome;
    protected readonly IBarCool _cool;

    public AbstractFoo(IAbstractParams params)
    {
     _awesome = params.awesome;
     _cool = params.cool;
    }

    ...heavy lifting
}

然后您需要添加 nuget 包 Autofac.Extras.AggregateService 并将此行添加到您的构建器:

builder.RegisterAggregateService<IAbstractParams>();

感谢 @Travis Illig@C.Evenhuis 帮助我提出这个解决方案。

对于同一问题的更复杂的解决方案,请查看 @Kitson88

【讨论】:

    猜你喜欢
    • 2017-08-04
    • 1970-01-01
    • 2011-09-08
    • 1970-01-01
    • 2018-04-08
    • 2016-06-12
    • 2011-06-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多