我正在开发一款游戏(编写自己的物理引擎),并尝试在编写时遵循优秀的设计。最近,我遇到了许多难以发现的错误,为了更容易地捕获这些错误,我一直在尝试编写单元测试。这很难做到,因为我的很多组件都紧密耦合,尤其是game
模块。
在我的game
模块中,我导出了一个单例类实例,它保存当前游戏状态,时间,游戏事件等。但是,在阅读this并通过研究如何减少这种耦合后,我决定尝试重写这个类,使它不再是单例。
我们的想法是使用GameState
类,并在任何地方传递这些对象,以便单元测试可以为测试创建最小的状态。大多数功能只是成为游戏状态的一个功能,并返回一个新的游戏状态。但是,我遇到了一些设计问题:
我的实体对象的位置和速度是根据当前时间计算的python属性。这意味着我不能简单地传入GameState
对象而不将其重写为函数(导致icky语法)。代码示例:
class Entity:
@property
def position(self):
"""Interpolate the position from the last known position from that time."""
# Here, the game class instance is referenced directly, creating coupling.
dt = game.game_time - self._last_valid_time
# x_f = x_i + v_i*t + 1/2 at^2
return self._position + self._velocity * dt + .5 * self._acceleration * dt**2
我已经完成了一些关于如何解决这个问题的研究,并且遇到了Dependency Injection。基本上,我可以将GameState的'getter'对象传递给每个实体的初始化器。所有GameState'getter'对象都会返回当前状态。示例GameStateGetter
类:
class GameStateGetter:
_CurrentState = None
def update(self, new_state):
GameStateGetter._CurrentState = new_state
def __getattr__(self, name):
# GameState object properties are immutable, so this can't cause issues
return getattr(GameStateGetter._CurrentState, name)
现在我的问题。
一个问题是用于更新当前游戏状态的界面(我已经定义了update
方法,但这看起来像一个奇怪的界面)。另外,鉴于全局变量的问题是不可预测的程序状态,这不会真正阻止它。
game
依赖项“注入”Entity
类的position
属性?理想情况下,我希望保持简单。 GameStateGetter
类听起来非常抽象(即使实现很简单)。如果我的属性可以隐式传递当前状态,那就太好了。
提前感谢您提供的任何帮助!
答案 0 :(得分:1)
我不确定第一个问题,但听起来有点牵强。
关于第二个问题:当进行单元测试时,你可以先导入游戏模块,然后将全局游戏状态修补为模拟游戏状态。从现在开始,所有功能/属性都将使用模拟游戏状态。我会选择单元测试框架的 setUp 方法。
编辑:
为什么不制作模块级属性" GameState"在游戏模块中。你可以从任何地方访问它。如果您想要GameState的副本,请在其上定义 clone (或__deepcopy__)方法。
答案 1 :(得分:1)
将GameStateProvider
注入Entity
构造函数是一个非常简单的起点,它为您提供了一些可以在您的逻辑变得更复杂时重构的内容。
但是,当状态发生变化时,无需使用新状态更新每个Entity
。如果提供者是一个对象,默认情况下它是可变的。 (为什么你想要一直在变化的东西,游戏状态,无论如何都是不可变的?)你可以改变提供者的current_time
属性,然后任何时候得到position
它将包含正确的价值。
模式如下:
class Entity(object):
def __init__(self, game_state_provider):
self.provider = game_state_provider
@property
def position(self):
# lazily evaluate position as a function of the current time
if self._last_valid_time == self.provider.current_time:
return self._position
self._last_valid_time = self.provider.current_time
self._position = // insert physics here
return self._position