游戏设计模式 #4 命令模式提炼总结

作者:浅墨
2017-02-03
2 17 0

引言

这是一篇超过万字的读书笔记,总结提炼了《Game Programming Patterns》(中译版《游戏编程模式》)一书中所有章节与内容的知识梗概。

我们知道,游戏行业其实一直很缺一本系统介绍游戏编程进阶技巧的书籍,而《游戏编程模式》的出现,正好弥补了这一点。

之前已经有提到过,不同于传统的出版方式,这本书是网络出版,然后 Web 版完全免费,更是在 Amazon 上具有罕见的五星评价,可见读者对其的好评程度之高。加之书中内容生动有趣,将各种经验之谈娓娓道来,实在是业界良心。

在这篇文章之前,我已经写了三篇相关的文章,但感觉一次一种模式的介绍,节奏太慢,就用这篇总结式的文章来把19种设计模式一次介绍完。由于篇幅原因,现将本文按照章节拆分为若干部分分别介绍。

文章的短版本:全书内容思维导图

以下是《游戏编程模式》一书的内容梗概,全书内容19种模式的思维导图:

v2-5b889398df7bdff213ca0591cfbc6c8e_r

目录与阅读指南

本文按照《游戏编程模式》书中顺序,

常用GOF设计模式

  1. 命令模式
  2. 享元模式
  3. 观察者模式
  4. 原型模式
  5. 单例模式
  6. 状态模式

序列型模式

  1. 双缓冲模式
  2. 游戏循环
  3. 更新方法

行为型模式

  1. 字节码
  2. 子类沙箱
  3. 类型对象

解耦型模式

  1. 组件模式
  2. 事件队列
  3. 服务定位器

优化型模式

  1. 数据局部性
  2. 脏标识模式
  3. 对象池模式
  4. 空间分区

对全书的19种模式分以下三个方面进行了介绍:

  • 要点
  • 使用场合
  • 引申与参考

依次介绍完19种模式之后,最终给出了一些更多的参考与学习资源。

需要注意,设计模式本身在理解上就比较抽象。而因为本文是设计模式内容的总结式介绍,所以理解坡度自然会比较陡。若总结的部分不太理解的地方,建议大家去阅读原文(在每种模式的“引申与参考”一部分都已经给出了链接),将这篇文章对原文的总结与原文结合起来理解,这样掌握起来会比较顺畅。

常用GOF设计模式

这一部分介绍了游戏开发中较为常用的六种GOF设计模式:

  • 命令模式
  • 享元模式
  • 观察者模式
  • 原型模式
  • 单例模式
  • 状态模式

命令模式 Command Pattern

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。

要点

  • 将一组行为抽象为对象,这个对象和其他对象一样可以被存储和传递,从而实现行为请求者与行为实现者之间的松耦合,这就是命令模式。
  • 命令模式是回调机制的面向对象版本。
  • 命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
  • 命令模式的优点有:对类间解耦、可扩展性强、易于命令的组合维护、易于与其他模式结合,而缺点是会导致类的膨胀。
  • 命令模式有不少的细分种类,实际使用时应根据当前所需来找到合适的设计方式。

使用场合

  • 命令模式很适合实现诸如撤消,重做,回放,时间倒流之类的功能。
  • 基于命令模式实现录像与回放等功能,也就是执行并解析一系列经过预录制的序列化后的各玩家操作的有序命令集合。

引申与参考

  • 最终我们可能会得到很多不同的命令类。为了更容易实现这些类,定义一个具体的基类,包含一些能定义行为的高层方法,往往会有帮助。可以将命令的主体execute()转到子类沙箱中。
  • 对象可以响应命令,或者将命令交给它的从属对象。如果我们这样实现了,就完成了一个职责链模式。
  • 对于等价的实例,可以用享元模式提高内存利用率。
  • 命令模式的Unity版本实现
  • 本节内容相关的英文原文
  • 本节内容相关的中文翻译

享元模式 Flyweight Pattern

享元模式,以共享的方式高效地支持大量的细粒度的对象。通过复用内存中已存在的对象,降低系统创建对象实例的性能消耗。

要点

  • 享元模式中有两种状态。内蕴状态(Internal State)和外蕴状态(External State)。
    • 内蕴状态,是不会随环境改变而改变的,是存储在享元对象内部的状态信息,因此内蕴状态是可以共享的。对任何一个享元对象而言,内蕴状态的值是完全相同的。
    • 外蕴状态,是会随着环境的改变而改变的。因此是不可共享的状态,对于不同的享元对象而言,它的值可能是不同的。
  • 享元模式通过共享内蕴状态,区分外蕴状态,有效隔离系统中的变化部分和不变部分。

使用场合

在以下情况都成立时,适合使用享元模式:

  • 当系统中某个对象类型的实例较多的时候。
  • 由于使用了大量的对象,造成了很大的存储开销。
  • 对象的大多数状态都可变为外蕴状态。
  • 在系统设计中,对象实例进行分类后,发现真正有区别的分类很少的时候。

引申与参考

观察者模式 Observer Pattern

观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

要点

  • 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
  • 我们知道,将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
  • 目前广泛使用的MVC模式,究其根本,是基于观察者模式的。
  • 观察者模式应用广泛,Java甚至将其放到了核心库之中(java.util.Observer),而C#直接将其嵌入了语法(event关键字)中。

使用场合

  • 当一个抽象模式有两个方面,其中一个方面依赖于另一个方面,需要将这两个方面分别封装到独立的对象中,彼此独立地改变和复用的时候。
  • 当一个系统中一个对象的改变需要同时改变其他对象内容,但是又不知道待改变的对象到底有多少个的时候。
  • 当一个对象的改变必须通知其他对象作出相应的变化,但是不能确定通知的对象是谁的时候。

引申与参考

原型模式 Prototype Pattern

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

要点

  • 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
  • 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
  • 使用原型模式拷贝对象时,需注意浅拷贝与深拷贝的区别。
  • 原型模式可以结合JSON等数据交换格式,为数据模型构建原型。

使用场合

  • 产生对象过程比较复杂,初始化需要许多资源时。
  • 希望框架原型和产生对象分开时。
  • 同一个对象可能会供其他调用者同时调用访问时。

参考与引申

单例模式 Singleton Pattern

保证一个类只有一个实例,并且提供了访问该实例的全局访问点。

要点

  • 单例模式因其方便的特性,在开发过程中的运用很多。
  • 单例模式有两个要点,保证一个类只有一个实例,并提供访问该实例的全局访问点。
  • 尽量少用单例模式。单例模式作为一个全局的变量,有很多全局的变量的弊病。它会使代码更难理解,更加耦合,并且对并行不太友好。

使用场合

  • 当在系统中某个特定的类对象实例只需要有唯一一个的时候。
  • 单例模式要尽量少用,无节制的使用会带来各种弊病。
  • 为了保证实例是单一的,可以简单的使用静态类。 还可以使用静态标识位,在运行时检测是不是只有一个实例被创建了。

参考与引申

状态模式 State Pattern

允许对象在当内部状态改变时改变其行为,就好像此对象改变了自己的类一样。

要点

  • 状态模式用来解决当控制一个对象状态转换的条件表达式过于复杂的情况,它把状态的判断逻辑转移到表示不同的一系列类当中,可以把复杂的逻辑判断简单化。
  • 状态模式的实现分为三个要点:
    • 为状态定义一个接口。
    • 为每个状态定义一个类。
    • 恰当地进行状态委托。
  • 通常来说,状态模式中状态对象的存放有两种实现存放的思路:
    • 静态状态。初始化时把所有可能的状态都new好,状态切换时通过赋值改变当前的状态。
    • 实例化状态。每次切换状态时动态new出新的状态。

使用场合

  • 在游戏开发过程中,涉及到复杂的状态切换时,可以运用状态模式以及状态机来高效地完成任务。
  • 有限状态机的实现方式,有两种可以选择:
    • 用枚举配合switch case语句。
    • 用多态与虚函数(即状态模式)。
  • 有限状态机在以下情况成立时可以使用:
    • 有一个行为基于一些内在状态的实体。
    • 状态可以被严格的分割为相对较少的不相干项目。
    • 实体可以响应一系列输入或事件。

参考与引申