BobDalgleish已经注意到,这种(反)模式称为“ 流失数据 ”。
以我的经验,流浪汉数据过多的最常见原因是有一堆链接状态变量,这些状态变量实际上应该封装在对象或数据结构中。有时,甚至有必要嵌套一堆对象以正确组织数据。
举个简单的例子,考虑一个游戏,有一个可定制的玩家角色,具有相似特性playerName
,playerEyeColor
等等。当然,玩家在游戏地图上也有实际位置,还有其他各种属性,例如当前和最大健康水平等等。
在此类游戏的第一次迭代中,将所有这些属性都设置为全局变量可能是一个完全合理的选择-毕竟只有一个玩家,并且游戏中的几乎所有东西都以某种方式涉及到了玩家。因此,您的全局状态可能包含如下变量:
playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100
但是到某个时候,您可能会发现需要更改此设计,可能是因为您想在游戏中添加多人游戏模式。第一次尝试,您可以尝试将所有这些变量都设置为局部变量,并将其传递给需要它们的函数。但是,您可能随后发现游戏中的某个特定动作可能涉及一个函数调用链,例如:
mainGameLoop()
-> processInputEvent()
-> doPlayerAction()
-> movePlayer()
-> checkCollision()
-> interactWithNPC()
-> interactWithShopkeeper()
...并且该interactWithShopkeeper()
功能使店主可以按名称向播放器地址,因此您现在突然需要playerName
通过所有这些功能作为流氓数据进行传递。而且,当然,如果店主认为蓝眼睛的玩家是幼稚的,并且会向他们收取更高的价格,那么您就需要playerEyeColor
遍历整个功能链,依此类推。
在适当的溶液中,在这种情况下,当然,以限定封装名称的选手对象,眼睛的颜色,位置,健康和游戏者角色的任何其他属性。这样,您只需要将单个对象传递给涉及播放器的所有功能。
同样,上面的几个功能自然可以成为该播放器对象的方法,这将自动使它们访问播放器的属性。从某种意义上讲,这只是语法上的糖,因为在对象上调用方法实际上会将对象实例作为隐藏参数传递给该方法,但是如果使用得当,它的确会使代码看起来更加清晰自然。
当然,典型的游戏将具有比玩家更多的“全局”状态。例如,您几乎肯定会拥有某种进行游戏的地图,以及在地图上移动的非玩家角色列表,以及放置在其上的物品,等等。您也可以将所有这些都作为流浪对象传递,但这又会使您的方法参数混乱。
相反,解决方案是让对象存储对与它们具有永久或临时关系的任何其他对象的引用。因此,例如,玩家对象(可能还包括任何NPC对象)可能应该存储对“游戏世界”对象的引用,该对象将对当前关卡/地图进行引用,从而player.moveTo(x, y)
无需使用类似的方法明确给定地图作为参数。
类似地,如果我们的玩家角色有一只宠物狗跟随它,我们自然会将描述该狗的所有状态变量归为一个对象,并为玩家对象提供对该狗的引用(以便玩家可以(例如,用名字叫狗),反之亦然(以便狗知道玩家在哪里)。并且,当然,我们可能希望使播放器和dog对象都成为更通用的“ actor”对象的子类,以便我们可以重用相同的代码,例如在地图上移动。
附言 即使以游戏为例,也有其他类型的程序也会出现此类问题。但是,以我的经验来看,潜在的问题往往总是相同的:您有一堆单独的变量(无论是局部变量还是全局变量),实际上想将它们聚集在一起成为一个或多个互连的对象。侵入函数中的“陷阱数据”是由“全局”选项设置还是由数值模拟中的高速缓存的数据库查询或状态向量组成的,解决方案始终是识别数据所属的自然上下文并将其变成对象(或您选择的语言中最接近的等价词)。