您的位置:首页 > 其它

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

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

The Prototype Design Pattern

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

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

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

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




实现它就像这样:

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,就像从不离开蜂房的蜂王一样。




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

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的语言。但是作为面向对象语言,它没有类。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: