3

I'm using dependency injection in my C# projects and generally everything is ok. Nevertheless, I often hear the rule "constructor must only consist of trivial operations - assign dependencies and do nothing more" I.e:

//dependencies
interface IMyFooDependency
{
   string GetBuzz();
   int DoOtherStuff();
}

interface IMyBarDependency
{
  void CrunchMe();
}

//consumer
class MyNiceConsumer
{
  private readonly IMyFooDependency foo;
  private readonly  IMyBarDependency bar;
  private /*readonly*/ string buzz;//<---question here
  MyNiceConsumer(IMyFooDependency foo, IMyBarDependency bar)
  { 
     //omitting null checks
     this.foo = foo;
     this.bar = bar;
     //OR
     this.buzz = foo.GetBuzz();//is this a bad thing to do? 
  }  
}

UPD: assume IMyFooDependency can't be replaced with the GetBuzz(), as in that case the answer is obvious: "do not depend on foo".

UPD2: Please, understand that this question is not about eliminating dependency from foo in a hypothetic code, but about understanding a principle of good constructor design.

So, my questions is following: is this really a bad pattern to include non-trivial logic in constructor(i.e. obtaining buzz value, making some calculations based on dependencies.)

Personally I, unless lazy load is necessary, would include foo.GetBuzz() in constructor, as object need to be initialized after call to its constructor.

The only drawback I see: by including non-trivial logic you increase the number of places where something might go wrong, and you'll get an obfuscated error message from your IoC container(but same things happen in case of invalid parameter, so the drawback is rather minor)

Any other considerations for eliding non-trivial constructors?

4

3 回答 3

1

如果您IMyFooDependency只需要buzz创建,那么您实际上需要嗡嗡声:

class MyNiceConsumer
{
  private readonly IMyBarDependency bar;
  private readonly string buzz;

  MyNiceConsumer(string buzz, IMyBarDependency bar)
  { 
     this.buzz = buzz;
     this.bar = bar;
  }  
}

并以这种方式创建好消费者的实例:

new MyNiceConsumer(foo.GetBuzz(), bar);

我看不出在将参数传递给构造函数之前获取嗡嗡声或在构造函数内部获取它之间有什么区别。将从存储库返回相同的值。因此,您不需要依赖存储库。

更新:从技术上讲,构造函数中复杂的初始化逻辑没有任何问题。看一下 winformsInitializeComponent方法,其中所有控件都被创建、初始化并添加到表单中。

但它违反了 SRP(创建和初始化)并且难以测试。您可以在编写可测试代码指南中阅读有关此缺陷的更多信息。大意:

不要在构造函数中创建协作者,而是将它们传递进去。(不要找东西!要东西!)

于 2012-05-14T21:53:10.957 回答
1

在构造函数中不做任何工作的理由来自于分两个阶段查看程序的执行。第一阶段是连接您的对象图。第二阶段是做“实事”。

这种理想与有效维护类的不变量和内部状态之间存在矛盾。您可以在构造函数中进行的设置越少,实现所有方法的难度就越大,因为它们必须考虑到对象可能变化的内部状态。请记住,构造函数是唯一可以确定为对象调用的代码。

解决这个难题的方法是认识到一个对象的“实际工作”是由它与其他对象相关的接口和行为定义的。也就是说,提供给构造函数的依赖项和作为参数提供给以后的方法的对象。

随意在构造函数中进行任何类型的设置,这些设置不会对系统中的其他对象产生明显影响。同样,对对象构造中的时间问题非常敏感。

如果您确定没有用户提供的文件名就不能存在 File 对象:不要在构造函数中调用 keyboard.filename_from_keyboard()。相反,您设计您的系统,以便在执行期间使用提供给构造函数的文件名由工厂(提供者)创建对象,或者您允许 File 对象在没有文件名的情况下存在。也许它可以在执行期间获得自己的文件名?这是管理对象生命周期的一部分,也是 IMO 中最难的部分。这变得非常微妙,因为“实际工作”也涉及创建对象。但我离题了...

在您的示例中,您必须确定 foo.GetBuzz() 是否打破该条件。如果 GetBuzz() 是一个引用透明函数,那么您几乎总是可以在构造函数中调用它。如果 GetBuzz() 涉及任何 I/O、用户交互或更改任何其他对象的任何明显内部状态,那么它可能不需要从构造函数中调用。

于 2012-05-15T16:36:04.950 回答
0

正如lazyberezovsky 正确提到的那样,不要寻找东西!问东西!

如果创建代码(比方说,MyNiceCreator)将foo其视为不透明的值并更新MyNiceConsumer,那么很可能创建不应该是MyNiceCreator. 创建MyNiceConsumer实例的代码必须能够为构造函数提供所需的值。更好的模式: MyNiceCreator应该“询问”一个MyNiceConsumer实例。这样,MyNiceConsumer实例的创建将由适当的类负责。

于 2012-05-15T00:37:03.140 回答