游戏编程模式:命令模式(Part I)

    下面进入本书的第二部分了,叫做设计模式回顾(Design Patterns Revisited)。这一部分有一个引言,本人翻译如下:

-----------------------------------------------------分割线---------------------------------------------------------------

        根据我的手表的时间,《设计模式:可复用的面向对象软件的元素》(Design Patterns: Elements of Reusable Object-Oriented Software,下面简称《设计模式》)这本书已经将近20年历史了。除非你站在我的肩膀上眺望,否则有很大的可能在你阅读此书的时候,《设计模式》的年纪已经大到可以喝酒了。对于像软件这样快速发展的行业来说,这实际上已经很古老了。但是这本书持续的流行度说明了,与许多框架和方法论(frameworks and methodologies)相比,设计是多么地不受时间限制。

        虽然我觉得《设计模式》仍然是有用的,但是我们在过去的二十年来学到了很多。在本节中,我们会浏览四人帮阐述过的一些原始的模式。对于每一个模式,我希望有一些有用的或者是有意思的东西可以说说。

        我觉得有一些模式被滥用了(比如说单例模式),而其他的一些被低估了(比如说命令模式)。还有一些出现在这里是因为我想要特别地探索它们在游戏中的作用(比如说享元模式和观察者模式)。最后,有时候我只是觉得看看模式在游戏这个比较庞大的程序领域中是如何被绊住的比较有趣(原型模式和状态模式)。

-----------------------------------------------------分割线---------------------------------------------------------------

 

    下面正式进入命令(Command)这一章的内容。

 

-----------------------------------------------------分割线---------------------------------------------------------------

        命令模式是我最喜欢的模式之一。我写的大部分大型程序,不管是不是游戏,最终都会在某处用到它。当我在正确的地方使用它时,它优美地理清了一些纠结的代码。对于如此一流的模式,四人帮不出所料地做了一个深奥的描述:

    将一个请求封装成一个对象,因此可以让用户用不同的请求来参数化客户,然后将请求做成队列或者记录成日志,并支持可撤销的操作。

        我觉得我们都可以同意这是一个糟糕的语句。首先,它把它想要建立的比喻给搞乱了。在单词可以表示任何意思的古怪的软件世界之外,一个“客户”是一个人——你与之做生意的人。我上次检查的时候,人类不能够被“参数化”。

        其次,该句子的其余部分不过是一些你可以、或许、很可能用该模式做的事情的列表。除非你的使用案例正好在这个列表里,否则这并不十分有启发性。对命令模式的言简意赅的(pithy)诠释是:

        一个命令是一个实体化(reified)的方法调用。

        当然,“言简意赅”经常意味着“简练得令人费解”,所以这也许并不是一个很大的改善。让我来稍微解释一下。“reify”,如果你没听说过的话,表示“使……变得真实”。另一个表示reifying的术语是让某物成为“第一等”(first-class)。

        某些语言中的反射系统(reflection systems)让你在你的程序中在运行时命令式地(imperatively)使用类型。你可以得到一个代表另外某个对象的类的对象,并且使用它来看这个类型可以做什么。换句话说,反射是一个实体化的类型系统(reified type system)。

        两个术语都表示取得某个概念,并将之转换成数据——就是一个对象——,并且该数据可以保存在一个变量中,传递给函数,等等。所以通过把命令模式说成是一个“实体化的函数调用”,我的意思是它是一个包裹在对象中的函数调用。

        这听上去很像“回调”(callback)、“第一类的函数”(first-class function)、“函数指针”、“闭包”(closure)或者“partially applied function”,取决于你来自哪种语言,并且这些确实都挺相近。四人帮然后又说了:

    命令是对回调的一种面向对象式的替换。

        这样的描述比之前的更适合该模式。

        但是所有的这些都是抽象的、朦胧的。我喜欢以具体的例子来开启章节,and I blew that. 为了补偿,从这儿开始,所有的都是适合使用命令模式的例子了。

1、设置输入

        在每个游戏的某处都有一块代码来读取用户的原始输入——按钮的按下、键盘的事件、鼠标的点击,等等。该代码取得每一个输入,并将之翻译成游戏中的有意义的行动:

        一个死板的、简单的实现看上去像这样:

void InputHandler::handleInput()
{
  if (isPressed(BUTTON_X)) jump();
  else if (isPressed(BUTTON_Y)) fireGun();
  else if (isPressed(BUTTON_A)) swapWeapon();
  else if (isPressed(BUTTON_B)) lurchIneffectively();
}

    小贴士:不要太频繁地按B键。

   

        这个函数通常由游戏循环来每帧调用一次,并且我确定你可以看出它做了什么。如果我们要将用户的输入固定地对应到游戏操作的话,那么该代码管用,但是很多游戏允许用户来设置这些按键将会被映射成什么操作。

        为了支持这一特性,我们需要把对jump()和fireGun()这些函数的直接调用转换成某种我们能够替换(swap out)的东西。“替换”听上去很像给一个变量赋值,所以我们需要一个对象用于代表一个游戏操作。接下来进入——命令模式。

        我们定义一个基类来代表可以触发的(triggerable)游戏指令:

class Command
{
  public:
  virtual ~Command() {}
  virtual void execute() = 0;
};

        如果你有一个接口,这个接口只有一个不返回任何东西的方法,那么很可能这是一个命令模式。

        然后我们为每个不同的游戏操作定义相应的子类:

class JumpCommand : public Command
{
public:
  virtual void execute() { jump(); }
};

class FireCommand : public Command
{
public:
  virtual void execute() { fireGun(); }
};

// 你懂意思了吧……


        在我们的输入处理器(input handler)中,我们为每个按钮储存一个指向命令的指针:

class InputHandler
{
public:
  void handleInput();

  // Methods to bind commands...
private:
  Command* buttonX_;
  Command* buttonY_;
  Command* buttonA_;
  Command* buttonB_;
};

        现在我们的输入处理就委托(delegate)给下面的代码了:

void InputHandler::handleInput()
{
  if (isPressed(BUTTON_X)) buttonX_ ->execute();
  else if (isPressed(BUTTON_Y)) buttonY_ ->execute();
  else if (isPressed(BUTTON_A)) buttonA_ ->execute();
  else if (isPressed(BUTTON_B)) buttonB_ ->execute();
}

        注意到我们并没有对NULL的情况进行检查了吗?这里假设了每个按钮都与某个命令绑定在了一起。

        如果我们想要在不显式地检查NULL的前提下支持什么都不做的按钮,我们可以定义一个其execute()方法什么都不做的命令类。这样,我们让按钮处理器指向该类的一个对象,而不是将其设为NULL。这就是NULL对象模式。

        在之前每个输入直接调用一个函数的地方,现在多了一个间接层(a layer of indirection):


        这就是命令模式的概要。如果你已经看到了它的价值,那么可以将本章剩余的部分当做额外奖励。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值