“类不应该在其构造函数中执行涉及依赖项的工作。”

时间:2010-08-26 17:15:54

标签: c# java oop dependency-injection

所以,引用来自"Dependency Injection in .NET"。考虑到这一点,下面的课程设计错误了吗?

class FallingPiece { //depicts the current falling piece in a tetris game
    private readonly IPieceGenerator pieceGenerator;
    private IPiece currentPiece;

    public FallingPiece(IPieceGenerator pieceGenerator) {
        this.pieceGenerator = pieceGenerator;
        this.currentPiece = pieceGenerator.Generate(); //I'm performing work in the constructor with a dependency!
    }

    ...
}

所以这个FallingPiece班级有责任控制俄罗斯方块游戏中当前掉落的棋子。当这件作品撞到底部或其他地方时,会发出一个事件信号,然后通过工厂生成另一件从上面再次开始落下的新件。

我看到的唯一选择是使用Initialize()方法生成该块,但是IMO有点违背了让构造函数将对象置于有效状态的想法。

6 个答案:

答案 0 :(得分:15)

一般来说,拇指规则就是:经验法则。一个人永远不应该偏离的不可改变的法则 - 我很确定你会发现在构造函数中使用依赖项做事情比其他情况更有意义。

考虑到这一点,让我们重新审视您的特定设计:

  

所以这个FallingPiece类有   控制权的责任   目前在俄罗斯方块中落下的一块   游戏。当这件作品击中底部时   或其他地方举起一个活动   发出信号然后通过   工厂,生成另一件新作品   从上面再次开始下降。

我觉得奇怪的是FallingPiece在完成后会触发片段生成器。

我的设计是这样的:

class PieceGenerator
{
    public FallingPiece NextFallingPiece()
    {
        FallingPiece piece = new FallingPiece(NextPiece());
        return piece;
    }
    // ...
}

class GameEngine
{
    public PieceGenerator pieceGenerator = new PieceGenerator();

    public void Start()
    {
        CreateFallingPiece();
    }

    void CreateFallingPiece()
    {
        FallingPiece piece = pieceGenerator.NextFallingPiece();
        piece.OnStop += piece_OnStop;
        piece.StartFalling();
    }

    void piece_OnStop(FallingPiece piece)
    {
        piece.OnStop -= piece_OnStop;
        if (!GameOver())
            CreateFallingPiece();
    }
}

至少在这种设计中,GameEngine完全负责告诉发电机何时创建它的碎片,这似乎比具有该任务的FallingPiece更惯用。

答案 1 :(得分:5)

是的,我会说某些东西不是那里设计的。很难说因为你没有说明如何使用FallingPiece或它如何适应系统,但它对我来说为什么它需要在其构造函数中获得currentPiece没有意义。

由于看起来currentPiece可能会随着时间的推移而发生变化,因此您似乎正在使用FallingPiece的单个实例作为当前下降的单件容器。在这种情况下,你必须以某种方式得到第二个,第三个等等。为什么不简单地为第一件做同样的事情?

你说当一件作品击中底部时,会发出一个事件,触发生成一件新作品开始下降。难道你不应该自己解雇这个事件来开始第一件事吗?

一般情况下:

在构造函数中不应该使用依赖项工作的一般原因(我认为)是对象的构造应该纯粹是关于设置类以便能够执行它需要做的事情。实际上的类应该通过在创建对象的API之后调用来完成。在FallingPiece的情况下,一旦它具有IPieceGenerator,它就有能力完成它需要做的所有事情。实际上这样做(开始下降)应该通过在其API上调用一个方法或者(在这里似乎就是这种情况)触发它将响应的事件来完成。

答案 2 :(得分:3)

正如其他人所指出的,这种说法确实是一个经验法则。然而,这是一个相当强大的规则,因为它为我们提供了不止一个好处:

  • 它将SRP应用于构造函数;换句话说,它使构造函数保持尽可能干净,因为它只做一件事。
  • 它使单元测试更容易,因为创建SUT不涉及复杂的Fixture Setup
  • 它使设计问题更加明显

在我看来,第三个好处是在这里发挥作用。如上所述,这个构造函数尖叫SRP违规。 FallingPiece课程的责任是什么?现在它似乎创造了新的作品并代表了一件堕落的作品。

为什么FallingPiece不是IPiece的实现?

总而言之,IPiece听起来像是应该是状态机。起初它正在移动并且可以被操纵,但是当它击中底部时它会被锁定到其他部分,直到它被移除。在改变状态时提升事件可能是有意义的。

从面向对象的角度来看,我认为游戏板应该有一组IPiece实例,它们知道它们在哪里以及它们可以做什么。

总之,我认为关于不在构造函数中工作的规则,因为它引起我们对设计问题的关注。

答案 3 :(得分:2)

我想说这个类设计得恰当,因为构造函数的参数列表需要IPieceGenerator。因此,它不违背这个想法,但是如果它是对参数列表中未包含的静态方法的调用,那么我认为它不符合该规则。

答案 4 :(得分:2)

如果您正在使用构造函数注入,这很好。

很明显为什么这不适用于二传手注射。我认为引用在后者方面非常有效。

答案 5 :(得分:1)

离开currentPiece null并不一定意味着将对象保持在单元化状态。 例如,您可以将currentPiece转换为属性,并在首次访问时将其初始化为懒惰。

这不会以任何方式影响公共接口,但会保持构造函数的轻量级,这通常是一件好事。