设计模式学习——原型模式

这篇博客详细介绍了原型模式(Prototype Pattern)的概念及其在 C++ 中的实现。原型模式通过克隆现有对象来创建新对象,从而提高性能和代码复用性。文章首先介绍了原型模式的基本原理,并通过一个游戏地图的示例展示了如何使用原型模式创建和管理实体对象。具体实现包括定义抽象类 Entity 及其子类 Enemy 和 Chest,并在地图类 Map 中存储和克隆这些实体对象。最后,文章总结了原型模式的优点,特别是在需要频繁创建对象的场景中,其高效性和灵活性。

简介

和原型模式相关的操作是“克隆”。更进一步地,原型模式并不是为了解决类的复制操作问题,而是为了将克隆操作放在接口类上,让其它类来使用这一接口,以此达到代码复用的目的。

在一些直接创建对象开销较大的情况下,我们也可以先返回某个类的克隆对象作为缓存,以此降低整体的开销。

例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。——菜鸟教程

原型

现在假设我们制作一个游戏地图,该地图能够存储实体的指针。我们可以对实体增加“克隆”操作,就能在地图中低开销地快速新增大量实体。在这个过程中,我们将实体作为一个带有“克隆”操作的抽象类Entity,它就是原型模式中的“原型”。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Entity {
public:
    explicit Entity() = default;
    virtual ~Entity();
    virtual Entity* clone() const = 0;
    int id() const;
protected:
    int _id = 0;
};

int Entity::id() const{return _id;}
Entity::~Entity() = default;

我们再继承原型创建其它具体的实体类,并实现具体的clone()方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//Enemy
class Enemy : public Entity {
public:
    Enemy();
    Entity* clone() const override;
};
Enemy::Enemy() : Entity() {
    _id = 1;
}
Entity* Enemy::clone() const{
    std::cout << "An Enemy was cloned." << std::endl;
    return new Enemy();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//Chest
class Chest : public Entity {
public:
    explicit Chest();
    Entity* clone() const override;
};
Chest::Chest() : Entity(){
    _id = 2;
}
Entity* Chest::clone() const{
    std::cout << "A Chest was cloned." << std::endl;
    return new Chest();
}

调用原型

接下来我们实现存储Entity*的Map对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Map {
public:
    explicit Map(Vector2D size);
    ~Map();

    void printMap() const;
    void addEntity(Entity*, Vector2D pos);
private:
    std::vector<std::vector<Entity*>> _map;
    //2D dynamic array
};

Map::Map(const Vector2D size) :_map(size.x, std::vector<Entity*>(size.y,nullptr)){}
Map::~Map() {/*......*/}
void Map::printMap() const {/*......*/}

void Map::addEntity(Entity* entity, const Vector2D pos) {
    if(pos.y < _map.size() && pos.x < _map[0].size())
        _map[pos.x][pos.y] = entity;
}

我们使用一个可变数组直接存储了Entity*变量,这大大提高了代码复用型。

在主函数中,我们可以创建不同的Entity*对象并将其大量克隆到地图中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int main() {
    auto *map = new Map(Vector2D(5,5));
    map->printMap();
    std::cout << "-------------------" << std::endl;

    auto enemy = new Enemy();
    for (int i = 0; i < 3; i++) {
        map->addEntity(enemy->clone(), Vector2D(randInRange(2,4),randInRange(2,4)));
    }
    map->printMap();
    std::cout << "-------------------" << std::endl;

    auto chest = new Chest();
    for (int i = 0; i < 2; i++) {
        map->addEntity(chest->clone(), Vector2D(randInRange(0,2),randInRange(0,2)));
    }
    map->printMap();

    return 0;
}

运行结果如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
-------------------
An Enemy was cloned.
An Enemy was cloned.
An Enemy was cloned.
0 0 0 0 0
0 0 0 0 0
0 0 0 0 0
0 0 0 0 1
0 0 1 0 1
-------------------
A Chest was cloned.
A Chest was cloned.
-------------------
0 0 0 0 0
0 0 2 0 0
2 0 0 0 0
0 0 0 0 1
0 0 1 0 1

总结

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。

在实际场景中,克隆可能会在父类和子类之间进行,并且可能是动态的。我们给父类提供一个虚克隆函数,就能通过使用父类指针来实现子类对象的拷贝。这本质上也是面向对象中多态的体现。