我对C ++很陌生,正在编写一个跨平台(桌面/移动)2D游戏引擎...我的问题是,我是否以适当的方式使用单身人士,如果没有,是否还有其他选择?< / p>
基本上我的引擎有一些组件是围绕单个对象构建的。例子:
VBOManager(Singleton)
这个“经理”基本上负责分配,当然还有“管理”用于存储纹理映射和顶点坐标的vbo。我通过这个对象控制“读/写”,所以我可以缓存由其他对象写入vbo的数据并返回指针(避免存储重复数据,如500个sprite,具有相同的映射和顶点坐标)。
TextureManager(Singleton,自我解释)
GLUtils(Singleton)
我几乎用它来统一基于当前平台不同的常见GL调用,如GL或GLES。例如,GLU函数(libglu不在android上,所以我有自定义实现)
图形(单身人士)
用于处理窗口初始化和调整大小,全局帧速率管理,视口初始化等事项。
InputManager(Singleton,自我解释)
无论如何所以在这里,我正在审查所有这些,我开始觉得真的,非常脏。我确实认为,鉴于给定对象的要求和功能,它是合理的。但另一方面,我是一个新手,而且我觉得这个“模式”在我的代码中如此猖獗是不对的。如果后者确实如此,那可能是另一种选择?
答案 0 :(得分:7)
在开始之前,我想澄清我们正在谈论的内容,以便我们都在同一页面上。单身人士是:
全局对象不是单身人士;它只是一个全球性的对象。如果可以制作多个,则不是单身。
现在定义已经不在了:
VBOManager(Singleton)
我无法想象这个物体如何以任何有意义的方式起作用。就OpenGL而言,缓冲对象是独立的构造。他们彼此没有关系。所以我不明白为什么你需要一个处理所有的经理。
我可以理解有一个专门的包装器缓冲区对象类来处理流缓冲区对象。有几种方法可以进行流式传输,有些方法比其他方式更有效。因此将它包装在一个对象中是有意义的。
但是拥有一个全局缓冲区对象管理器是没有意义的。缓冲对象应该彼此独立。或者,如果有的话,它们应该依赖于其他对象,如网格(多个网格对象可以引用相同的缓冲区)。但是你不需要有一个全局缓冲区对象管理器。
我可以理解拥有一个可以添加和检索的命名对象的存储库。但是我不明白是否需要将它用于像这样的特定对象类型。并且它需要成为一个全球性的单身人士......可疑。
我发现单身人士最适合从根本上独特的概念。有一个文件系统,因此有一个全局文件系统是有意义的。 OpenGL本身是全局的(由于其基于C的特性),尽管如此,它仍然可以切换渲染上下文。如果有两个东西是有道理的,那么单身可能不是最佳方式。
即使你只使用一个缓冲对象,并且有一个管理器,也没有必要让这个类成为单例。如果您愿意,可以将其设置为全局,但不需要强制阻止用户创建此类的多个实例。如果以后,你想拥有多个缓冲区对象管理器(并可能支付性能价格),那就这样吧。
GLUtils(Singleton)
为什么这是对象? Singleton模式指的是一个对象,任何时候都只能有一个实例。实用程序功能只是全局功能。
C ++不是Java。您不必将所有内容都放在课堂上。全局功能设计也不错。实际上,它们是适当的好设计。如果一个函数不需要访问任何状态(在它的参数和它可能调用的任何其他函数之外),那么就不需要它成为一个对象的一部分。
图形(单身)
有人可以说单身人士是有道理的。它是一个有国家的生物。并且您明确地不希望在您的应用程序中使用多个。
然而,考虑一下。拥有多个人是不是错了?你多久会绕过这个Graphics
对象?需要多少代码才能在Graphics
对象的级别上工作?我猜不是很多。一些基本的初始化代码,就是这样。
因此虽然它是可辩护的,但它并非必要。这不是一个需要成为单身人士的概念。此外,通过能够拥有多个,您可以随时删除并创建一个新的。这使您的应用程序能够更轻松地重建窗口。
这样做的一个缺陷是可能同时拥有两个Graphics
个对象。这涉及到处理OpenGL上下文。由于OpenGL上下文是全局状态,因此具有多个Graphics
对象可能会导致问题,因为两者都有自己的上下文。您需要一些方法将特定的Graphics
对象设置为当前的对象,它将自身绑定为当前的OpenGL上下文(并检索它需要的任何函数指针)。
答案 1 :(得分:4)
全局变量&lt;单身&lt;面向对象的设计模式
单身人士并不可怕,只有总是更好的方式。他们的问题真的开始出现在有几个人的大型项目上 - 如果你将它们用于一个小小的个人项目那么我觉得你不需要重新构建整个设计就是为了避免它们。
答案 2 :(得分:3)
我之前听说设计模式“单身人士”实际上被称为反模式,我认为这是一个有趣的想法。
虽然它可能非常有用,但如果过度使用它基本上最终会使你的代码成为c-like,因为它使所有东西都可以访问(它将你的类转变为全局)。如果你的单例类中有很多公共方法,那么几乎可以从程序的所有部分访问程序的所有操作。
这几乎从来都不是面向对象的好设计。虽然它可能会对你起作用,但如果你允许其他人修改你的代码,他们就会开始意识到 - 嘿,我为什么要这样做才能直接调用呢?
随着时间的推移,程序可能会降级并变得像意大利面条逻辑。
有时即使只有一个事物的实例,最好将该实例包含在更高级别的实体中作为普通成员。如果你这样做,以便更高级别的实体是一个单独的(即使我不一定推荐),你几乎可以保证只有一个成员实例。谁知道呢?也许有人会找到一个你没有想过的课程中不止一个的理由。
最重要的是,它在概念上很容易理解,并且您的设计可以很好地封装您的数据。
话虽如此,我已经编制了一些程序,在这些地方,作为单身人士在全球范围内可以使用的东西似乎值得失去程序的优秀OO设计。在一天结束时,这是对你的判断。如果你开始制作太多的单身人士,那么首先要考虑其他设计它的方式,这将有更传统的面向对象的布局 - 我敢打赌,你会发现9/10倍的其他设计更好而不是单身人士。
答案 3 :(得分:3)
我也认为你在虐待单身人士。对您列举的示例的建议:
VBOManager
管理VBO只对拥有3D网格的类感兴趣。将其转换为Mesh类中的私有或受保护的类变量和成员,以便只有网格可以访问此功能。
TextureManager
与维也纳国际组织相同的想法。这应仅对纹理实例可见,因此这应该在Texture类中作为类成员/变量。
GLUtils
这个我不确定,因为我不清楚它是如何工作的。这甚至需要成为一个对象吗?如果这只是一个常见图形函数的集合,那么它只是一些命名空间下的一堆函数吗?
图形
你说这是窗口,视口等的初始化代码。一旦应用程序初始化,你需要在这个对象中调用方法吗?似乎你的main()
函数需要有其中一个为应用程序运行设置适当的环境,但是一旦完成,你可能不需要使用这个对象,所以也许你可以把它放在范围内main()
,以便当应用程序退出时,它可以破坏窗口并干净地退出。
输入管理
根本不确定你是否需要这个。它有什么作用?您显然需要拥有Event对象,但通常这些事件会通过某种回调发送到应用程序,因此应用程序永远不需要与InputManager对象进行通信。相反,您可以拥有一个侦听事件的后台线程,当收到一个事件时,它会将其放入应用程序读取的事件队列中,或通过回调函数将其发送给感兴趣的侦听器。
答案 4 :(得分:3)
我认为单身人士是一种反模式。让我们回想一下单身是什么:它是一个只能创建一个实例的类。因此,单身人士总是全球性的。技术上,单身人士是美化名称空间。如果你用“命名空间的全局变量集和相关的操作函数”替换“singleton”,你在语义上是一样的。
单身人士几乎没有任何用途。
VBOManager(Singleton) 这个“经理”基本上负责分配,当然还有“管理”用于存储纹理映射和顶点坐标的vbo。我通过这个对象控制“读/写”,所以我可以缓存由其他对象写入vbo的数据并返回指针(避免存储重复数据,如500个sprite,具有相同的映射和顶点坐标)。
VBO与OpenGL上下文绑定,但也可以在OpenGL上下文之间共享。因此,有多个VBOManager实例,每个OpenGL上下文或一组共享上下文都是有意义的。
TextureManager(Singleton,自解释)
和VBO一样。纹理与上下文相关联,但可以共享。
GLUtils(Singleton) 我几乎用它来统一基于当前平台不同的常见GL调用,如GL或GLES。例如,GLU函数(libglu不在android上,所以我有自定义实现)
这绝对需要命名空间。然而,在GLUTesselator,GLUQuadric等类中封装功能并允许多个实例更有意义吗?
图形(单身)
您可以拥有多个窗口和上下文。
InputManager(Singleton,自解释)
输入可能来自多个来源,每个窗口都可能生成它。在网络游戏中,您还可以处理多个输入频道。
看起来,游戏只有一个世界/地图加载,或者一个输入系统。但如果您可以加载多个世界/地图会有伤害吗?想一想:能够保留多个地图可以实现更多的东西,比如从另一个地图渲染动态天空盒,在后台加载下一个地图,然后在正确的位置交换指针。
我个人认为没有单身人士的理由。
答案 5 :(得分:1)
我的经验是,单身人士要么在任何地方明确宣布,要么被商业案例暗示。就个人而言,我更喜欢它们,因为当我使用别人的代码时它会非常清楚它是如何工作的。
可能投票 - 坚持下去 - 你做得很好。
答案 6 :(得分:1)
这取决于。 0)正如其他人所说,单身(或更好地说,全局状态)是有意义的,如果它们代表只存在一次的东西(例如文件系统,但它真的只有一个吗?)
但为什么要反对呢? (而不是全局函数/变量)
1)封装 - 如果必须维护有意义的不变量,那么只有通过维护它的函数才能访问它。如果它只是很多独立变量,那么使它成为对象并没有帮助,你可以直接访问它们。
2)继承 - 如果你想使用不同的实现,例如。配置这么说,你可以让工厂返回一些子类。你可以在没有对象的情况下做到这一点,但是你会发现重新发明虚函数。
在某些情况下,最好使用“有效”单身 - 因为你只需要一个,你只使用一个,但如果你开始需要更多,你可以拥有更多。 因此,在代码的“常规”部分,实例应该传递给函数,因此它不会被锁定为默认值,而“business”代码只要你只需要一个就可以使用“singletons”。