依赖注入&单身设计模式

时间:2010-04-18 15:55:21

标签: design-patterns oop dependency-injection

我们如何确定何时使用依赖注入或单例模式。 我在很多网站上都读到过“使用依赖注入超过单一模式”的网站。但我不确定我是否完全赞同他们。对于我的中小型项目,我肯定会看到单例模式的使用直截了当。

例如Logger。我可以使用Logger.GetInstance().Log(...)  但是,为什么我需要使用记录器的实例注入我创建的每个类?

7 个答案:

答案 0 :(得分:82)

单身人士就像共产主义一样:他们在纸上听起来都很棒,但在实践中却出现了问题。

单例模式过分强调访问对象的难易程度。它通过要求每个消费者使用AppDomain范围的对象完全避开上下文,不为不同的实现留下任何选项。它在您的课程中嵌入基础知识知识(对GetInstance()的调用),同时添加表达能力。它实际上会降低你的表现力,因为你不能改变一个类使用的实现而不改变它们的所有。你根本无法添加一次性功能。

此外,当课程Foo取决于Logger.GetInstance()时,Foo会有效地隐藏其与消费者的依赖关系。这意味着您无法完全理解Foo或自信地使用它,除非您阅读其来源并揭示它取决于Logger这一事实。如果您没有源代码,则会限制您理解和有效使用您所依赖的代码的程度。

使用静态属性/方法实现的单例模式只不过是实现基础结构的黑客攻击。它以无数种方式限制了你,同时对替代方案没有任何明显的好处。您可以随意使用它,但由于有可行的替代方案可以促进更好的设计,因此绝不应该推荐它。

答案 1 :(得分:52)

如果要验证测试中记录的内容,则需要依赖注入。此外,记录器很少是单例 - 通常每个类都有一个记录器。

Watch this presentation关于面向对象的可测试性设计,你会发现为什么单身人士不好。

单身人士的问题在于他们代表了一个难以预测的全球状态,特别是在测试中。

请记住,一个对象可以是事实上的单例,但仍然可以通过依赖注入获得,而不是通过Singleton.getInstance()获得。

我只是列举了Misko Hevery在他的演讲中提出的一些重要观点。在观看之后,您将全面了解为什么最好让对象定义的依赖关系,而不是定义如何创建它们的方式。

答案 2 :(得分:17)

其他人已经很好地解释了单身人士的问题。我想添加一个关于Logger特定情况的说明。我同意你的观点,通过静态getInstance()getRootLogger()方法访问Logger(或准确地说是根记录器)通常不是一个问题。 (除非你想看看你正在测试的课程记录了什么 - 但根据我的经验,我很难回想起这种必要的情况。再说一次,对于其他人来说这可能是一个更紧迫的问题。)

IMO通常单独记录器不用担心,因为它不包含与您正在测试的类相关的任何状态。也就是说,记录器的状态(及其可能的变化)对测试类的状态没有任何影响。因此,它不会使您的单元测试更加困难。

另一种方法是通过构造函数将记录器注入(几乎)应用程序中的每个类。为了保持接口的一致性,即使所讨论的类目前没有记录任何内容,也应该注入它 - 另一种选择是,当你在某个时候发现 now 时,你需要从这个类中记录一些东西,你需要一个记录器,因此你需要为DI添加一个构造函数参数,打破所有客户端代码。我不喜欢这两种选择,我觉得使用DI进行日志记录只会使我的生活变得复杂,以符合理论规则,而没有任何具体的好处。

所以我的底线是:一个(几乎)普遍使用的类,但不包含与您的应用相关的状态,可以安全地实现为Singleton

答案 3 :(得分:9)

这主要是,但不是关于测试的。 Singltons很受欢迎,因为它很容易消费它们,但单身人士有许多缺点。

  • 难以测试。这意味着我如何确保记录器做正确的事。
  • 难以测试。这意味着如果我正在测试使用记录器的代码,但这不是我测试的重点,我仍然需要确保我的测试环境支持记录器
  • 有时你不想要唱歌,但更灵活

DI让您轻松使用您的依赖类 - 只需将它放在构造函数args中,系统就可以为您提供 - 同时为您提供测试和构建灵活性。

答案 4 :(得分:2)

关于你应该使用Singleton而不是依赖注入的唯一时间是Singleton表示一个不可变的值,例如List.Empty等(假设不可变列表)。

Singleton的肠道检查应该是“如果这是一个全局变量而不是Singleton,我会没事吗?”如果没有,你使用Singleton模式来描述全局变量,并且应该考虑不同的方法。

答案 5 :(得分:1)

刚刚查看了Monostate文章 - 它是Singleton的一个漂亮的替代品,但它有一些奇怪的属性:

class Mono{
    public static $db;
    public function setDb($db){
       self::$db = $db;
    }

}

class Mapper extends Mono{
    //mapping procedure
    return $Entity;

    public function save($Entity);//requires database connection to be set
}

class Entity{
public function save(){
    $Mapper = new Mapper();
    $Mapper->save($this);//has same static reference to database class     
}

$Mapper = new Mapper();
$Mapper->setDb($db);

$User = $Mapper->find(1);
$User->save();

这不是那种可怕 - 因为Mapper真的依赖于数据库连接来执行save() - 但是如果之前已经创建了另一个mapper - 它可以 在获取它的依赖项时跳过此步骤。虽然整洁,但它也有点凌乱不是吗?

答案 6 :(得分:0)