消除"游戏"单身中的耦合和全局状态

Cas*_*all 5 python singleton unit-testing pygame python-3.x

我正在开发一个游戏(编写我自己的物理引擎),并尝试在编写时遵循优秀的设计.最近,我遇到了许多难以发现的错误,为了更容易地捕获这些错误,我一直在尝试编写单元测试.这很难做到,因为我的很多组件都紧密耦合,尤其是game模块.

在我的game模块中,我导出一个单例类实例,它保存当前游戏状态,时间,游戏事件等.但是,在阅读了这篇文章并通过研究如何减少这种耦合后,我决定尝试重写这样的类,它不再是单身人士了.

我们的想法是使用一个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
Run Code Online (Sandbox Code Playgroud)

我已经做了一些关于如何解决这个问题的研究,并且遇到了依赖注入问题.基本上,我可以将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)
Run Code Online (Sandbox Code Playgroud)

现在我的问题.

  • 使用GameState'getter'对象会是个好主意吗?

一个问题是用于更新当前游戏状态的界面(我已经定义了一个update方法,但这看起来像一个奇怪的界面).另外,鉴于全局变量的问题是不可预测的程序状态,这不会真正阻止它.

  • 是否有另一种方法将game依赖"注入" Entity类的position属性?

理想情况下,我想保持简单.一个GameStateGetter类听起来非常抽象(即使实现很简单).如果我的属性可以隐式传递当前状态,那将是很好的.

提前感谢您提供的任何帮助!

Rob*_*ney 1

将 a 注入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
Run Code Online (Sandbox Code Playgroud)