《游戏程序设计模式》 1.4 - 原型模式

    我第一次听到”原型“这个词是在《设计模式》中。今天,好像每个人都在谈论这个词,但是结果并不是在谈论设计模式。我们会讲到它,但是我也会展示其他的这个词和其后的概念出现过的地方。但首先,让我们先回顾一下原始的模式。

The Prototype Design Pattern

    假设我们正在做一个挑战类型的游戏。各种生物和恶人成群结队围绕着英雄,争着要吃掉他的肉体。这些令人讨厌的吃友通过“生成器”进入竞技场,每种敌人都有一种生成器。

    在这个例子中,不同的怪物有不同的类-鬼,恶魔,魔法师等:

class Monster
{
  // Stuff...
};
class Ghost : public Monster
{};
class Demon : public Monster
{};
class Sorcerer : public Monster
{};

    一个生成器构造一种怪的实例。为了支持所有的怪,我们会暴力地为每种怪定义一个生成器,形成平行的类层级关系:

    03171055_YIJv.png

    实现它就像这样:

class Spawner
{
public:
  virtual ~Spawner() {}
  virtual Monster* spawnMonster() = 0;
};
class GhostSpawner : public Spawner
{
public:
  virtual Monster* spawnMonster()
  {
    return new Ghost();
  }
};
class DemonSpawner : public Spawner
{
public:
  virtual Monster* spawnMonster()
  {
    return new Demon();
  }
};
// You get the idea...

    除非你是按代码行数领工资的,否则这显然不是一种有趣的方法。大量的类,大量的样板,大量的冗余,大量的重复,大量的重复我说的话……

    原型模式提供了一种解决方法。核心思想就是,一个对象可以产生类似于自身的其他对象。如果你有一个鬼,你可以产生更多的鬼。如果你有一个恶魔,你可以产生其他恶魔。任何怪物都可以作为原型产生其他的怪。

    为了实现其它,我们为基类Monster定义一个纯虚函数Clone():

class Monster
{
public:
  virtual ~Monster() {}
  virtual Monster* clone() = 0;
  // Other stuff...
};

    每一个Monster子类提供一个实现,返回与自身属性相同的新对象。例如:

class Ghost : public Monster 
{
public:
  Ghost(int health, int speed)
  : health_(health),
    speed_(speed)
  {}
  virtual Monster* clone()
  {
    return new Ghost(health_, speed_);
  }
private:
  int health_;
  int speed_;
};

    一旦所有Monster都支持了,我们就不需要为每种怪定义一个生成器了,我们只需定义一个即可:

class Spawner
{
public:
  Spawner(Monster* prototype)
  : prototype_(prototype)
  {}
  Monster* spawnMonster()
  {
    return prototype_->clone();
  }
  private:
  Monster* prototype_;
};

    它的内部定义一个Monster,其唯一的目的是用作模板产生更多类似的Monster,就像从不离开蜂房的蜂王一样。

    03171059_Dblj.png

    为了创建一个鬼生成器,我们创建一个鬼的实例用作原型,然后创建一个生成器绑定这个实例:

Monster* ghostPrototype = new Ghost(15, 3);
Spawner* ghostSpawner = new Spawner(ghostPrototype);

    这个模式整洁的一点是,它不仅产生原型类型的对象,还复制了它的状态。这意味着我们可以产生快鬼,弱鬼,慢鬼只需要创建相应的原型即可。

    我发现了这个模式一些优雅又惊讶的地方。我不敢想象是我自己提出来的,我更不敢想象我现在竟然还不了解它。

How well does it work?

    我们不必为每个Monster定义一个生成器类,这很好。但是我们必须为每个Monster实现clone()方法。这跟定义spawner的代码差不多一样多。

    当你准备写正确的clone()函数时,你会发现有几个恶劣的陷阱。它是深复制还是浅复制?换句话说,就是如果一个恶魔拿着一把干草叉,复制恶魔时要不要复制干草叉?

    我们中的大多数知道过多的类层级会很难管理,这就是为什么我们使用component和type object模式的原因,使用这些模式可以对不同的实体进行分类建模不至于它们混在一起。

spawn functions

    即使为每个Monster创建一个spawner,我们还是有其他的方式来实现的。相比为每个Monster定义一个spawner,我们可以定义函数,就像这样:

Monster* spawnGhost()
{
  return new Ghost();
}

    这样就比定义一个类少点恶心。然后唯一的spawner类就可以存储一个函数指针:

typedef Monster* (*SpawnCallback)();
class Spawner
{
public:
  Spawner(SpawnCallback spawn)
  : spawn_(spawn)
  {}
  Monster* spawnMonster()
  {
    return spawn_();
  }
private:
  SpawnCallback spawn_;
};

    为了创建一个鬼的spawner,你会这么做:

Spawner* ghostSpawner = new Spawner(spawnGhost);

Templates

    现在,大多数C++的程序员都对模板比较熟悉了。我们要创建产生某种Monster类型的spawner实例,但是我们不想硬编码特定的Monster类型。自然的方法就是使它变成类型参数,这正是模板要做的:

class Spawner
{
public:
  virtual ~Spawner() {}
  virtual Monster* spawnMonster() = 0;
};
template <class T>
class SpawnerFor : public Spawner
{
public:
  virtual Monster* spawnMonster() { return new T(); }
};

    使用起来就像这样:

Spawner* ghostSpawner = new SpawnerFor<Ghost>();

First-class Types

    以上两个方案解决了需要根据类型生成一个spawner实例的问题。在c++中,并不是所有类型都是“一级”的,所以需要一些变通。如果你使用动态类型语言,像JavaScript,Python和Ruby这种类就是普通的对象可以随便传递的语言,你就解决这个问题更容易。

    当你要生成一个spawner时,你只要把要产生Monster对象传进去。so easy。

    所有这些选项,我承认我没有找到一个案例使用原型模式是最好的解决方法。也许你有不同的经验,但是现在我们放下这点,谈点别的:原型模式作为语言范式。

The Prototype language paradigm

    许多人认为“面向对象”与“类”就是同义词。相比结构型语言C和函数式语言scheme,“面向对象”的特点就是将数据和行为紧绑在一起。

    你可能认为类是实现它的唯一方式,但是一小部分人包括Dave Ungar和Randall Smith。他们创造了一种叫做Self的语言。但是作为面向对象语言,它没有类。


转载于:https://my.oschina.net/xunxun/blog/487133

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值