突然之间,我有一些OO危机。在过去的几年里,我已经很好地利用了Singleton对象。我在很多地方都使用它们。
例如,在设计MVC Java应用程序时,我创建了一个Singleton'SystemRegistry'类来存储模型和视图类(我只处理过简单的应用程序,并且不需要多个视图)。
当我创建模型并查看对象(不是单例,只是普通对象)时,我会做类似的事情:
SystemRegistry.getInstance().setModel(model);
在我的控制器类(几乎是不同GUI项的事件处理程序)中,我可以访问视图或模型,如下所示:
SystemRegistry.getInstance().getView();
我永远不会在我的应用程序的模型部分中使用SystemRegistry类,但有时会在我的视图中使用它来访问模型中的信息(但很少,如果有的话,修改)。
从我读过的内容(特别是Steve Yegge's article)来看,这似乎是设计我的应用程序的一种糟糕方式。关于更好地构建代码的方法的任何想法。
另外,我设计类的另一个方面是使用'Manager-type'类,这些类可能与Singletons有关,也可能没有。一个例子是我用C ++创建的(非常简单的)基于OpenGL的游戏引擎。
主要课程是GameEngine。正是过度激励的类存储了一堆经理并处理了主循环而没有。存储在这个类中的一些管理器是这样的: ObjectManager,RenderingManager,LightingManager,EventManager(包括输入),HUDManager,FrameRateManager,WindowManager等。可能还有一些。
基本上这些类处理游戏引擎的不同方面。名称非常简单,因此您应该能够很好地了解它们的使用方式。
现在,这本来是一个可重用的基础,我可以在不同的项目中使用,需要理想地改变它。
在每个新游戏中,我将创建一个GameEngine实例作为一个类范围的变量(大多数游戏逻辑存储在一个类中)并设置不同的管理器(例如,加载窗口共同从文件中纵坐标或照明细节,设置FPS等)。要在ObjectManager中注册对象,我会执行以下操作:
Player player = new Player();
gameEngine.getObjectManager().addObject(player);
此对象现在将存储在ObjectManager类的向量中,并在GameEngine在每个帧中调用ObjectManager drawObjects()方法时绘制。
在Singletons的文章之后我可能会有点偏执(可能没有足够的时间来绕过它),但我开始猜测并想知道我设计GameEngine的方式是正确的(因为没有更好的词)并且不仅仅陷入了单身人士模式所共有的陷阱。
对我的帖子发表任何评论都会非常感激。
修改:感谢您的回答。我非常感激他们。如果可能的话,如果有人能给我一些关于上面发布的两个项目方案的提示,我会很高兴。我怎么能避免使用单身人士/经理?
对于第一个,DI是否是正确的答案?我是否应该给视图访问模型(这可能更像是一个MVC响应)?该视图是否会受益于实现接口(以便可以插入多个不同的视图)?
在第二种情况下,如何构建应用程序呢?抱怨只是使用经理类而不是更具体的名称?或者是,在某些情况下,类可以进一步细分(例如ObjectHolder,ObjectDrawer,ObjectUpdater)?
答案 0 :(得分:11)
答案 1 :(得分:4)
关于你的原始问题,所有人都已经说过,所以关于编辑中的后续问题。
不要混淆只有一个类的实例与单例模式。在您的情况下,拥有一个管理各种事物的经理类是很好的。在整个应用程序中只有一个实例也很酷。但是使用管理器的类不应该依赖于一个全局对象的存在(无论是通过类的静态变量还是所有人都可以访问的全局变量)。为了缓解这种情况,使它们在构造时需要对manager-object的引用。他们都可以接受对同一个对象的引用,他们也不在乎。这有两个很大的副作用。首先,您可以更好地测试您的类,因为您可以轻松地注入模拟管理器对象。第二个好处是,如果您稍后决定,您仍然可以使用多个管理器对象(例如,在多线程方案中),而无需更改客户端。
答案 2 :(得分:3)
我认为单身人士存在一些问题:
但有时你真的做必须只有一个。我不会被带有模式综合症的小男孩带走。
答案 3 :(得分:1)
最近,当我查看我的一个项目并看到“经理”课程(主要是单身人士)的激增时,我有类似的认识。看来你的担忧是双重的 - 首先,是否要使用全球状态(导致经理类型),其次,单身人士是否是全球国家的最佳选择。
我不认为它真的是黑白的。有时你有实体应该只有一个实例,并且应该可以从你的程序中的任何地方访问,在这种情况下,全局状态解决方案是有意义的。如果非全局替代方法涉及创建实体的实例并通过层上的方法和构造函数层(以及类构造函数的层次结构)传递对它的引用,则会很方便。
另一方面,人们需要考虑全球状态的弊端。它隐藏了依赖关系。一旦引入全局变量,函数就不再是黑盒子,其输入仅限于它们的参数。如果你有可变的全局状态,可以通过多个线程中的多个类以任何顺序访问,你可以在手上进行调试噩梦。这一切都使得理解/测试变得更加困难。
当选择全局状态时,有时静态类比单例更好(还有其他选项)。首先,它节省了额外的GetInstance()调用......对我而言,它感觉更自然。但值得注意的是,单身人士自动允许延迟加载,如果您的全球实体的初始化相当重要,这可能很重要。此外(在这里有点做作),如果您需要提供全局行为的变体,您可以简单地为您的单例子类化。
答案 4 :(得分:0)
我决定是否需要单身的方式就是当我回答这个问题:“如果我在同一个运行时将我的应用程序实例两次,它会有意义吗?”,如果答案是肯定的,那么我使用它
答案 5 :(得分:0)
两步回答:
1)http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/
单身人士是好的设计,如果他们被创建以确保只存在一个实例。如果创建它们以确保全局访问,则它们是糟糕的设计。
2)http://misko.hevery.com/2008/08/21/where-have-all-the-singletons-gone/
使用依赖注入来抵消全局访问。