书中总结出这种 Subclass Sandbox 的设计模式

Game Design Patterns: Subclass Sandbox

这种模式要点有两点:

  1. 在基类中实现各种功能性方法供子类调用
  2. 定义沙盒接口供子类重载

书中的例子,基类中定义三个实用方法,和沙盒接口 activate:

class Superpower
{
public:
virtual ~Superpower() {} protected:
virtual void activate() = 0; void move(double x, double y, double z)
{
// Code here...
} void playSound(SoundId sound, double volume)
{
// Code here...
} void spawnParticles(ParticleType type, int count)
{
// Code here...
}
};

然后在子类中调用

class SkyLaunch : public Superpower
{
protected:
virtual void activate()
{
// Spring into the air.
playSound(SOUND_SPROING, 1.0f);
spawnParticles(PARTICLE_DUST, 10);
move(0, 0, 20);
}
};

这两个要点其实是独立的。书中主要在讨论第一点。第二点,沙盒接口的作用,一个是执行入口,另一个是调用时机可控,这些大多是和具体业务相关。

看书中的例子,很明显能看出一个问题:

void move(double x, double y, double z) 和其他两个方法playSoundspawnParticles并不是一类功能。假设基类抽象的是场景节点,move是实例本身的功能,放到基类作为公用代码以及公用接口是OOP的标准用法。因此主要考虑的是,实用的功能性方法,如播放声音,播放特效等,应该放到哪里?

主要有几种方案:

  1. 直接写:每次在用到的地方编写一遍功能。显然这会造成代码的冗余,给维护造成麻烦。但是往往很多功能一开始写的时候就是写在用到的地方,在开发过程中逐渐由更多的地方需要同样的功能时,再考虑把功能重构到通用的地方。

  2. 功能单例:不同类功能封装到不同的单例。如播放音乐时调用AudioEngine::getInstance()->playSound()。这样做的缺点,如书中所说,是程序中多个地方造成了对AudioEngine这样的系统的引用耦合。我们可以用加一层门面来抵消这种负面效果,如加一个AudioUtils类对原始的AudioEngine进行封装。

  3. 超级单例:不同类功能封装到同一个单例。如一个GameUtils类或直接放到GameRuntime类中,这样与“功能单例”相比的好处是,从依赖多个系统到依赖一个系统,并且减少整个程序单例的数量,调用的时候非常方便。缺点也是十分严重,就是GameUtils会是一个非常庞大的类,一方面增加这个类本身的阅读和维护成本,另一方面对降低了重用性。

    比如一个game有audio, graphics, physics功能,现在有另一个游戏要重用原有代码但是不需要physics特性,但是如果由于GameUtils依赖了physics系统,虽然在业务流程里没有任何调用任何物理相关的功能,要去除对physics系统的依赖也是很困难的。

    很多大型系统里,这种通用全能的类往往会变成一个泥潭,没人敢改动和删减其中的功能,只有不断往上加新的功能。

  4. 超级基类:把功能封装到基类中。就是书中介绍的方法。我对这种方法持保留态度。它实际是超级单例的一种特殊实现,缺点与其类似,甚至更糟。

    如果说超级单例方案造就了一个泥潭,超级基类把这坨泥潭引导每个实例中。它引入了继承这一大耦合,并且使得基类是一个会经常改动的类,这些都是反设计原则的。

    最可怕的是,如果你要用基类中的功能,就需要把自己变成它的子类。如果上一种方法是你要一片树叶给你一个森林,这个方法就是要把自己变成一个森林。

    一般基类中要有一些真正属于公有的代码,如引用计数,生成id,初始化等等,还有一些真正属于对象功能的方法。如果把功能方法和实用方法混在一起,对象真正的功能就会淹没在大量无关代码里,是阅读和维护代码的人很难抓住真正的功能。

    但这种方法在实践中还是用的很多的。我觉得最大的好处是实用方便,大多数语言里调用基类方法比调用其他全局实例方法的语法简洁的多。这常常意味着易用性,用 playSound() 显然比 AudioEngine::getInstance()->playSound() 爽快的多。特别是对初学者,或者非专业人员,比如封装给策划用的脚本,还有现在很多引擎面向的是非专业编程人员,写代码的时候感觉要什么有什么,根本不用考虑这些功能是哪里来的,反正是引擎提供的,只要专注游戏逻辑就好了。这些受众也不会太在意代码架构方面的东西。

  5. 消息系统:基于消息调用功能。这是依赖最低的方法。模块间的功能使用消息系统调用,如AudioEngine监听 PLAY_SOUND 消息,播放声音时发送 sendMessage(PLAY_SOUND, "a.mp3"),不用真正依赖 audio 系统。这样只要消息接口一致,调用方和被调用方可以独立重构。如果去掉某个模块,只是对应消息无效,不会代码崩溃或者无法编译。如果不想 AudioEngine 依赖消息系统,可以用建立一个 AudioManager 监听消息和调用 AudioEngine

    缺点也很多。1. 这种方法调用太重,有一些很小的功能或者实用函数不适合这种模式;2. 代码流会变得更乱,不容易阅读和调试;3. 调用变成纯动态,跳过编译器检查,容易出错;4. 效率会更低

游戏设计模式:Subclass Sandbox模式,以及功能方法集的设计思考的更多相关文章

  1. 设计模式---对象创建模式之工厂方法模式(Factory Method)

    前提:“对象创建”模式 通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定.它是接口抽象之后的第一步工作. 典型模式(表现最为突出) 工 ...

  2. 【C#设计模式——创建型模式】工场方法模式

    工场方法模式对简单工场模式进行了乔庙的扩展,不是用一个专门的类来决定实例化哪一个子类.相反,超类把这种决定延迟到每个子类.这种模式实际上没有决策点,就是没有直接选择一个子类实例化的决策. 看书上的例子 ...

  3. java设计模式之模板模式以及钩子方法使用

    1.使用背景 模板方法模式是通过把不变行为搬到超类,去除子类里面的重复代码提现它的优势,它提供了一个很好的代码复用平台.当不可变和可变的方法在子类中混合在一起的时候, 不变的方法就会在子类中多次出现, ...

  4. 设计模式之工厂模式之工厂方法(php实现)

    github: git@github.com:ZQCard/design_pattern.git /** * 工厂方法 * 使用开闭原则来分析下工厂方法模式.当有新的产品产生时,只要按照抽象产品角色. ...

  5. Delphi 设计模式:《HeadFirst设计模式》Delphi2007代码---工厂模式之工厂方法[转]

      1   2{<HeadFirst设计模式>工厂模式之工厂方法 }   3{ 产品类                              }   4{ 编译工具 :Delphi20 ...

  6. 游戏编程技巧 - Subclass Sandbox

    Subclass Sandbox 使用场景 你正在开发一款类似LOL的游戏,里面有许多英雄角色,你决定把这些英雄类交给小弟们实现.因为在这些英雄中,释放放技能时,有的要使用粒子系统造成炫酷的效果,有的 ...

  7. 游戏开发设计模式之状态模式 & 有限状态机 & c#委托事件(unity3d 示例实现)

    命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 原型模式:游戏开发设计模式之原型模式 & unity3d ...

  8. 游戏开发设计模式之原型模式 & unity3d JSON的使用(unity3d 示例实现)

    命令模式:游戏开发设计模式之命令模式(unity3d 示例实现) 对象池模式:游戏开发设计模式之对象池模式(unity3d 示例实现) 实现原型模式 原型模式带来的好处就是,想要构建生成任意独特对象的 ...

  9. 游戏开发设计模式之命令模式(unity3d 示例实现)

    博主才学尚浅,难免会有错误,尤其是设计模式这种极富禅意且需要大量经验的东西,如果哪里书写错误或有遗漏,还请各位前辈指正. 打 算写设计模式的目的就是,首先自己可以理清思路,还有就是国内的设计模式资料很 ...

随机推荐

  1. 【Linux高频命令专题(1)】sort

    介绍 sort命令是帮我们依据不同的数据类型进行排序,其语法及常用参数格式: sort [-bcfMnrtk][源文件][-o 输出文件] 补充说明:sort可针对文本文件的内容,以行为单位来排序. ...

  2. 银联接口(注意项&备忘)

    1,参考文档“证书下载.导出及上传流程.docx” 按照文档上所述,依次进行,导出的证书备用,用于配置文件的项“const SDK_ENCRYPT_CERT_PATH” 2,使用tp框架 新建一个控制 ...

  3. CreateTwoArray

    public class CreateTwoArray{ public static void main(String []args){ int[][]arr=new int [2][3]; Syst ...

  4. Spring与Struts2整合VS Spring与Spring MVC整合

    Spring与Struts2整合,struts.xml在src目录下 1.在web.xml配置监听器 web.xml <!-- 配置Spring的用于初始化ApplicationContext的 ...

  5. 未能加载文件或程序集“Interop.jmail”或它的某一个依赖项

    未能加载文件或程序集“Interop.jmail”或它的某一个依赖项.试图加载格式不正确的程序. 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该错误以及代码中 ...

  6. JDynamic :支持Json反序列化为Dynamic对象

    JDynamic :支持Json反序列化为Dynamic对象   2010年 .NET 4.0 发布前后,从3.5向4.0迁移,那时也有一些异构系统的需求,主要是和PHP打交道,通信使用的HTTP 格 ...

  7. 12个目标跟踪方面的资料12 Tracking

    Goal Tracking Template - FEMA.gov Goal Tracking Template Set a weekly or biweekly deadline to report ...

  8. awk当中使用外部变量

    1.awk命令使用双引号的情况下 此时在awk命令里面使用\"$var\"就可以引用外部环境变量的var的值 $ var="BASH";echo "u ...

  9. Android开发之通过反射获取到挂断电话的API

    Android开发黑名单工具类,需要用到挂断电话的API,但是该API处于隐藏状态,需要通过反射得到该方法.. 步骤: 1.通过当前类获取到ServiceManager的字节码 Class< ? ...

  10. ZOJ 3790 Consecutive Blocks (离散化 + 暴力)

    题目链接 虽然是一道暴力的题目,但是思路不好想.刚开始还超时,剪枝了以后1200ms,不知道为什么还是这么慢. 题意:给你n个点,每个点有一种颜色ci,给你至多k次删除操作,每次删除一个点,问最多k次 ...