我们如何确定何时使用依赖注入或单例模式。我在许多网站上都读过,他们说“对单例模式使用依赖注入”。但是我不确定我是否完全同意他们的观点。对于我的中小型项目,我肯定看到单例模式的使用很简单。
例如记录器。我可以使用Logger.GetInstance().Log(...)
But,而不是使用它,为什么我需要使用记录器的实例注入我创建的每个类?
Answers:
单身人士就像共产主义:在纸面上听起来都很棒,但在实践中却遇到了问题。
单例模式过分强调访问对象的难易程度。它要求每个使用者都使用AppDomain范围的对象,从而完全避开了上下文,而对于各种实现都没有选择余地。它将基础设施知识嵌入到您的类中(对的调用GetInstance()
),同时添加了完全为零的表达能力。实际上,它降低了您的表达能力,因为您不能更改一个类使用的实现,而不必为所有这些类更改。您根本无法添加一次性功能。
同样,当classFoo
依赖于时Logger.GetInstance()
,Foo
可以有效地将其依赖项对消费者隐藏。这意味着您不能完全理解Foo
或使用它,除非您阅读它的来源并发现它依赖的事实Logger
。如果您没有源代码,那将限制您理解和有效使用所依赖代码的能力。
用静态属性/方法实现的单例模式仅是围绕实现基础结构的技巧。它以多种方式限制了您,同时没有任何其他替代品的明显好处。您可以根据需要使用它,但是由于有可行的替代方法可以促进更好的设计,因此永远不建议这样做。
其他人通常已经很好地解释了单例的问题。我只想添加有关Logger特定情况的注释。我同意您的看法,通过静态getInstance()
或getRootLogger()
方法以单例方式访问Logger(准确地说是根记录器)通常不是问题。(除非您想查看正在测试的班级记录了什么,但是以我的经验,我很难回想起这种情况是必须的。对于其他人,这可能是一个更紧迫的问题)。
IMO通常不担心单例记录器,因为它不包含与您正在测试的类相关的任何状态。也就是说,记录器的状态(及其可能的更改)对测试类的状态没有任何影响。因此,它不会使您的单元测试变得更加困难。
替代方法是通过构造函数注入记录器,以(几乎)应用程序中的每个类。为了确保接口的一致性,即使所讨论的类当前未记录任何内容,也应注入该接口-替代方法是,当您发现某个时刻现在需要从该类中记录某些内容时,需要一个记录器,因此您需要为DI添加构造函数参数,从而破坏所有客户端代码。我不喜欢这两个选项,并且我觉得使用DI进行日志记录只会使我的生活变得复杂,以遵守理论规则,而没有任何具体的好处。
因此,我的底线是:可以(几乎)普遍使用但不包含与您的应用相关的状态的类,可以安全地实现为Singleton。
主要是测试,但并不是全部。Singlton之所以受欢迎是因为它很容易食用,但是单身人士有很多缺点。
DI使您可以轻松使用依赖类-只需将其放在构造函数args中,系统就会为您提供它-同时为您提供测试和构造灵活性。
刚刚查看了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-它可以跳过此步骤来获取其依赖项。虽然很整洁,但也很杂乱,不是吗?
Singleton还有其他替代方法:Proxy和MonoState模式。
http://www.objectmentor.com/resources/articles/SingletonAndMonostate.pdf