单身通常是件坏事(例如,请参阅here和here),我想避免它们。 单例的典型用例我还没有替代方法,当你有多个对象必须引用某种父/全局对象时。
示例1 :您有某种树,每个叶子对象都必须知道根对象。 (这是糟糕的设计,但仅仅是为了这个例子)
示例2 :您的“全局”配置对象通常像global_config::get_instance()->get_item(name)
问题:对于给定的示例 - 除了广泛的依赖关系传递和每个实例存储(aka Dependancy Injection
)之外,单例还有其他概念吗? ?
答案 0 :(得分:2)
单身人士只解决一件事:"static initialization order fiasco"全局对象以未定义的顺序实例化,因此他们不能相互依赖。
否则,单身人士只是全球可访问的对象,恰好只存在一次。
因此对于容器,如第一个示例中的树,全局实例并不比单例更差。此外,配置(示例2)主要是启动时反序列化的设置容器,因此没有什么能阻止您将其设置为正常的全局实例。
更好的设计是在"应用程序中为应用程序分组全局变量" class,所以至少可以清楚它们是应用程序范围的对象,并且无论如何都应该在启动时填充配置。
更好(对于更大的应用程序)是使模块中的应用程序像更大的对象一样,并传递所需的"全局变量"来自"应用程序" class有一个更清晰的依赖视图。这些模块还可以包含更大的对象,这些对象接收对" globals"的引用,但只要它们知道如何访问它们的模块化全局变量,就不必去每个对象。
选择更多依赖注入方法的最后一个论点是,您的对象将更容易进行模块测试,因为您可以更轻松地注入测试对象,并且所有具有状态的全局变量都会干扰一系列测试。
答案 1 :(得分:1)
我有时觉得有用的一种方法是允许get_instance()
方法接收一些参数并根据此参数返回不同的对象,如下所示:global_config::get_instance(string serviceName)
。如果你有几个单例类,你可以使用相同的参数来获取它们,因此每个用户只需要知道一个参数,而不是保留对所有单例的引用。
首先,对get_instance()
的所有调用都可以返回相同的对象而忽略参数,但如果开发要求您为应用程序的不同部分设置不同的单例,则可以通过使用不同的部分来调整代码应用程序将不同的参数传递给get_instance()
。
此外,这也将解决测试应用程序的问题,因为您可以找到一种方法将特殊的模拟参数传递给get_instance()
以接收模拟对象。
这可能对您的global_cofig
示例很有用,但对于您的树示例,您绝对应该保留对叶子中根的引用。
答案 2 :(得分:1)
问题:对于给定的示例 - 除了广泛的依赖关系传递和每个实例存储(aka
Dependency Injection
)之外,单例还有其他概念吗? ?
在这两种情况下,您都在谈论信息的可见性要求,不是单身(即只有一个对象实例)要求。
Craig Larman's "Applying UML and Patterns"表示对象 A 有四种方式可以访问对象 B :
属性可见性 - B是A的属性。
参数可见性 - B是A方法的参数。
本地可见性 - B是A方法中的(非参数)本地对象。
全局可见性 - B在某种程度上是全局可见的。
最后一个是Singleton提供的。这就是为什么这种模式存在争议的原因。
依赖注入是第一个选项 - B在其构造函数中注入A。
一般经验法则:在设计中暴露一条信息的次数越多,以后更改设计就越困难(因为耦合和依赖于该信息)。全局可视性使您可以在短期内轻松解决设计中的一些访问问题。由于过度暴露信息,它有长期存在问题的风险。