现在,在遇到一些问题后,阅读this SO问题,特别是this博客文章,我理解了我带给世界的邪恶。
那么:我如何从现有代码中删除 单例?
例如:
在零售店管理程序中,我使用了MVC模式。我的Model对象描述了商店,用户界面是View,我有一组控制器,它们充当两者之间的联络。大。除了我将Store变成一个单独的(因为应用程序一次只管理一个商店),我还把我的大部分Controller类变成了单例(一个mainWindow,一个menuBar,一个productEditor ......)。现在,我的大多数Controller类都可以访问其他单例:
Store managedStore = Store::getInstance();
managedStore.doSomething();
managedStore.doSomethingElse();
//etc.
我应该改为:
Globals仍然很糟糕,但至少它们不会是pretending。
我看到#1迅速导致构造函数调用非常膨胀:
someVar = SomeControllerClass(managedStore, menuBar, editor, sasquatch, ...)
还有其他人经历过这个吗?如果不是全局变量或单个变量,那么为多个单独的类访问公共变量的OO方法是什么?
答案 0 :(得分:19)
Dependency Injection是你的朋友。
查看excellent Google Testing Blog上的这些帖子:
希望有人为C ++世界制作了一个DI框架/容器?看起来Google发布了C++ Testing Framework和C++ Mocking Framework,这可能对您有帮助。
答案 1 :(得分:3)
我避免单身人士的方法源于“应用程序全局”并不意味着“虚拟机全球化”(即static
)的观点。因此,我引入了一个ApplicationContext
类,它包含许多应用程序全局的前static
单例信息,就像配置存储一样。此上下文将传递到所有结构中。如果您使用任何IOC容器或服务管理器,则可以使用它来访问上下文。
答案 2 :(得分:3)
这不是单身人士的问题。有一个只有一个实例的对象是没关系的。问题是全球访问。您使用Store的类应该在构造函数中接收Store实例(或者具有可以设置的Store属性/数据成员),并且它们都可以接收相同的实例。 Store甚至可以在其中保留逻辑,以确保只创建一个实例。
答案 3 :(得分:3)
在程序中使用全局或单例没有任何问题。不要让任何人因为那种废话而欺骗你。规则和模式是很好的经验法则。但最终这是你的项目,你应该自己判断如何处理涉及全球数据的情况。
无限制地使用全局变量是个坏消息。但只要你勤奋,他们就不会杀了你的项目。系统中的某些对象应该是单例。标准输入和输出。你的日志系统。在游戏中,您的图形,声音和输入子系统,以及游戏实体的数据库。在GUI中,您的窗口和主要面板组件。您的配置数据,插件管理器,Web服务器数据。对于您的应用程序而言,所有这些内容或多或少都具有全局性。我认为你的Store类也会传递它。
很清楚使用全局变量的成本是多少。您的应用程序的任何部分都可能正在修改它。当每行代码都是调查中的嫌疑人时,追踪错误很难。
但是不使用全局变量的成本呢?就像编程中的其他一切一样,这是一种权衡。如果避免使用全局变量,则最终必须将这些有状态对象作为函数参数传递。或者,您可以将它们传递给构造函数并将它们保存为成员变量。当你有多个这样的对象时,情况就会恶化。您现在线程您的州。在某些情况下,这不是问题。如果您知道只有两个或三个函数需要处理该有状态Store对象,那么这是更好的解决方案。
但在实践中,情况并非总是如此。如果您的应用程序的每个部分都触及您的商店,您将把它线程化为十几个功能。最重要的是,其中一些功能可能具有复杂的业务逻辑。当您使用辅助函数打破业务逻辑时,您必须 - 更多地处理您的状态!比如说你意识到深度嵌套的函数需要来自Store对象的一些配置数据。突然,您必须编辑3或4个函数声明以包含该store参数。然后你必须返回并将商店添加为实际参数,以调用其中一个函数。可能是函数对Store的唯一用途是将它传递给需要它的某个子函数。
模式只是经验法则。你是否始终使用你的转向信号才能改变你的车道?如果你是普通人,你通常会遵循这个规则,但如果你是在凌晨4点开空行驶的,那么谁会给你一个废话,对吗?有时它会咬你的屁股,但这是一个有管理的风险。
答案 4 :(得分:2)
关于膨胀的构造函数调用问题,您可以引入参数类或工厂方法来为您利用此问题。
参数类将一些参数数据移动到它自己的类,例如像这样:
var parameterClass1 = new MenuParameter(menuBar, editor);
var parameterClass2 = new StuffParameters(sasquatch, ...);
var ctrl = new MyControllerClass(managedStore, parameterClass1, parameterClass2);
它只是将问题转移到其他地方。您可能希望改为维护构造函数。只保留构造/启动相关类时重要的参数,并使用getter / setter方法(或者如果你正在使用.NET)执行其余操作。
工厂方法是一种创建类所需的所有实例的方法,并且具有封装所述对象的创建的好处。它们也非常容易从Singleton重构,因为它们与您在Singleton模式中看到的getInstance方法类似。假设我们有以下非线程安全的简单单例示例:
// The Rather Unfortunate Singleton Class
public class SingletonStore {
private static SingletonStore _singleton
= new MyUnfortunateSingleton();
private SingletonStore() {
// Do some privatised constructing in here...
}
public static SingletonStore getInstance() {
return _singleton;
}
// Some methods and stuff to be down here
}
// Usage:
// var singleInstanceOfStore = SingletonStore.getInstance();
很容易将其重构为工厂方法。解决方案是删除静态引用:
public class StoreWithFactory {
public StoreWithFactory() {
// If the constructor is private or public doesn't matter
// unless you do TDD, in which you need to have a public
// constructor to create the object so you can test it.
}
// The method returning an instance of Singleton is now a
// factory method.
public static StoreWithFactory getInstance() {
return new StoreWithFactory();
}
}
// Usage:
// var myStore = StoreWithFactory.getInstance();
用法仍然相同,但你不会因为拥有一个实例而陷入困境。当然,您可以将此工厂方法移动到它自己的类中,因为Store
类不应该关注自身的创建(并且巧合地遵循单一责任原则作为移动工厂方法的效果)。
从这里你有很多选择,但我会把它作为锻炼给自己。这里很难对模式进行过度设计(或过热)。我的提示是仅在有need for it时才应用模式。
答案 5 :(得分:1)
好吧,首先,“单身人士总是邪恶的”概念是错误的。只要有资源不会或不能复制,就可以使用Singleton。没问题。
那就是说,在你的例子中,应用程序中有一个明显的自由度:有人可以过来说“但我想要两个商店。”
有几种解决方案。首先出现的是建造一个工厂阶级;当你要求一个商店时,它会给你一个带有一些通用名称的名字(例如,一个URI)。在那个商店里面,你需要确保多个副本不会通过关键区域或某些方法相互踩踏。确保交易的原子性。
答案 6 :(得分:1)
Miško Hevery有一篇关于可测试性的精彩系列文章,其中包括singleton,他不仅讨论问题,还讨论如何解决问题(参见'修复漏洞') “)。
答案 7 :(得分:1)
我喜欢鼓励在必要时使用单身人士,同时不鼓励使用单身人士模式。注意单词的区别。单例(小写)用于只需要一个实例的地方。它在程序开始时创建,并传递给需要它的类的构造函数。
class Log
{
void logmessage(...)
{ // do some stuff
}
};
int main()
{
Log log;
// do some more stuff
}
class Database
{
Log &_log;
Database(Log &log) : _log(log) {}
void Open(...)
{
_log.logmessage(whatever);
}
};
使用单例提供了Singleton反模式的所有功能,但它使您的代码更容易扩展,并且使其可测试(在Google测试博客中定义的单词意义上)。例如,我们可能决定在某些时候也需要能够登录到Web服务,使用我们可以轻松完成的单例,而无需对代码进行重大更改。
相比之下,Singleton模式是全局变量的另一个名称。它从未在生产代码中使用过。