10

我一直在编写棋盘游戏的软件版本。到目前为止,我已经编写了与游戏板上的物理对象相对应的类。我很擅长编写程序逻辑,但是我发现许多逻辑类都需要访问相同的对象。

起初我将适当的对象传递给它们被调用的方法,但这变得非常乏味,特别是当方法需要许多对象来执行它们的任务时。为了解决这个问题,我创建了一个类来初始化和存储我需要的所有对象。例如,这允许我通过调用 Assets.dice() 从任何类访问对象。

但现在想来,这似乎不对。这就是我在这里的原因,我担心我创造了某种神级。这种恐惧是没有根据的,还是我创造了灾难的秘诀?

4

5 回答 5

7

您基本上遇到了单例模式。在大多数情况下,这是一个糟糕的模式。通过允许您的应用程序的任何部分几乎在任何时候访问基本上像这样的全局数据,您最终会得到一堆难以维护、调试,最重要的是测试的意大利面条式代码。

我认为最好创建一个“上下文”,其中包含当前的骰子、碎片等,并根据需要将上下文传递给需要使用它的方法/类。这要干净得多,尽管是的,到处都必须通过它是很痛苦的。但是您获得的优势是您可以跟踪谁在何时访问 Context,并且您还可以创建模拟 Context 用于测试目的。如果您将上下文传递给高级对象,并且它必须将其传递给它的子组件,那么您从头到尾都知道上下文是什么以及它来自哪里。

此外,理想情况下,使 Context 不可变也很好。那可能是不可能的。但是,如果对于每个给定的回合,如果您可以创建一个新的 Context 来捕获当前状态并且是不可变的,那么您可以减少应用程序带来的更多惊喜。

于 2010-04-23T15:29:59.457 回答
2

听起来您在询问面向对象编程的一般哲学。通常,您会发现将真实世界的对象建模为类并不总是最适合您的代码。

一个帮助我弄清楚这些东西的指导方针是两个拟人化类之间的对话(如果有人知道这句话的原始来源,我会很感激一个链接!):

A 类对 B 类说:“给我 x 的值。”

B 类:“你为什么要 x 的值?”

A班:“这样我就可以把它法兰了。”

B班:“问我,我给你法兰。”

这有助于使人们明白一个类旨在封装数据对其执行操作。总的来说,这是一个帮助我更好地组织代码的寓言。

您可能想要查看的另一件事是一些常见的面向对象设计模式。像游戏骰子这样的东西作为Singleton可能更有意义,因为您不需要多个实例。

如果您想对设计模式有很好的介绍,我建议您阅读优秀的Head First Design Patterns书。

于 2010-04-23T15:31:46.897 回答
0

拥有那种可以访问所有内容的上下文类与拥有全局变量非常相似。同样的缺点也适用。可以通过任何方法读取和更改全局变量。这将使用全局变量的所有内容相互耦合。耦合是不好的,因为当事物耦合时,一个对象的变化可能会导致另一个对象发生某些事情。当耦合程度增加时,管理复杂性变得非常困难(在我们的玩家类中,蝴蝶拍打翅膀可能会导致您的骰子类出现异常)。改变和额外的发展变得更加困难。难以检测和掩盖错误变得不可避免。

因此,例如,有一天在测试您的应用程序时,您可能会注意到您的 dice 对象表现得很奇怪。您调用 dice.roll() 并看到它有时会返回 1。但在这种情况下这是不可能的,因为您的玩家正在滚动其中两个。您调试并以某种方式注意到 numberOfDice 属性在某些时候更改为 1。使用像您这样的上下文方法,很难找到谁将 numberOfDice 更改为 1,因为每个人都可以通过您的上下文对象访问 dice。你明白了吗?

那么解决方案是什么?我喜欢的面向对象编程的隐喻之一是分而治之。你需要找到可以相互隔离的行为、过程、对象。您需要将问题划分为可管理的部分,这些部分可以与应用程序中发生的其他事情隔离开来。

学习如何做到这一点当然不容易,需要大量的学习、阅读、思考、讨论,当然还有编码。

于 2010-04-23T22:05:51.607 回答
0

它真的是一个“上帝”类,还是仅仅是一个“上下文”,它是一个游戏的所有相关对象实例的“容器”,然后传递给不同的方法调用(这样你只有一个参数)?后者很常见,我认为它没有任何问题,但在这种情况下,容器本身并没有真正的功能。

于 2010-04-23T15:31:22.320 回答
0

谢谢你提出这个问题。一段时间以来,我一直在想同样的问题。跨方法传递对象并限制参数需要创建可以容纳所有这些对象的类。

我仍然认为设计还不错,因为您确实需要一个跨多个层的域类。如果这个 Domain 类没有携带必要的对象并且没有执行任何逻辑,那应该没问题。

于 2010-04-23T15:31:33.823 回答