原型模式

定义

原型模式也是一种创建型模式,它用来复制一个现有的对象,而不是通过实例化创建一个新的对象。用一句话总结就是,当 new 一个对象很复杂或者开销很大(比如需要访问数据库或者文件系统),可以通过复制一个已经存在的实例来创建新对象,从而提高性能。

其中,“已存在的实例”就是指原型对象。

原型模式用来解决的问题

  1. 性能问题:当创建一个对象的成本十分昂贵时,例如需要访问数据库、进行复杂的网络请求、设计大量计算或者I/O操作时,使用原型模式可以通过复制现有对象来避免这些开销,从而提高性能。每次都通过 new 来初始化会严重影响性能。如果能有一个“模板”对象,后续的对象直接从这个模板复制,就可以绕过昂贵的初始化过程。

  2. 解耦和灵活性:当系统需要动态地创建和配置对象,而不想在代码中硬编码具体的类时。客户端代码可以与具体的类解耦,只需与一个抽象的原型接口打交道,通过这个接口克隆出所需的对象。

结构

原型模式的结构通常包含一下几个部分

  1. Prototype 接口: (原型接口/抽象类)

    • 声明一个克隆自身的方法,通常命名为 clone()。这个接口定义了所有原型对象必须实现的克隆操作。
    • 在C++中,这通常是一个抽象基类,包含一个纯虚的 clone() 方法。
  2. ConcretePrototype 类:(具体原型类)

    • 实现 Prototype 接口,提供具体的克隆逻辑。每个具体原型类都需要实现 clone() 方法,返回一个新的对象实例,该实例是当前对象的副本。
  3. Client 类: (客户端)

    • 持有一个原型对象的引用。
    • 当需要新对象时,请求原型对象克隆自身,从而得到一个新的、一模一样的对象。
    • 客户端可以对克隆出的新对象进行修改,而不会影响到原始的原型对象。

示例

我们用一个经典场景举例。假设我们有一个图形库,其中包含圆形Circle()和矩形Rectangle(),创建这些对象可能会有一些初始设置,我们需要根据不同场景复制不同图形对象。

#include <iostream>
#include <string>
#include <vector>

// 1. Prototype (原型抽象基类)
class Shape {
public:
    Shape() = default;
    Shape(const Shape& source) { // 拷贝构造函数
        this->x = source.x;
        this->y = source.y;
        this->color = source.color;
    }

    virtual ~Shape() {}

    // 纯虚的 clone 方法
    virtual Shape* clone() const = 0;

    void setPosition(int x, int y) {
        this->x = x;
        this->y = y;
    }

    virtual void draw() const {
        std::cout << "Drawing a shape at (" << x << ", " << y << ") with color " << color << std::endl;
    }

protected:
    int x = 0, y = 0;
    std::string color = "black";
};

// 2. ConcretePrototype (具体原型类 - Circle)
class Circle : public Shape {
public:
    Circle() = default;
    Circle(int radius, const std::string& color) : radius(radius) {
        this->color = color;
    }
    
    // 拷贝构造函数,用于 clone
    Circle(const Circle& source) : Shape(source) {
        this->radius = source.radius;
    }

    // 实现 clone 方法
    Shape* clone() const override {
        return new Circle(*this); // 核心:调用拷贝构造函数
    }

    void draw() const override {
        std::cout << "Drawing a circle with radius " << radius 
                  << " at (" << x << ", " << y << ") with color " << color << std::endl;
    }

private:
    int radius = 0;
};

// 2. ConcretePrototype (具体原型类 - Rectangle)
class Rectangle : public Shape {
public:
    Rectangle() = default;
    Rectangle(int width, int height, const std::string& color) : width(width), height(height) {
        this->color = color;
    }

    // 拷贝构造函数,用于 clone
    Rectangle(const Rectangle& source) : Shape(source) {
        this->width = source.width;
        this->height = source.height;
    }
    
    // 实现 clone 方法
    Shape* clone() const override {
        return new Rectangle(*this);
    }
    
    void draw() const override {
        std::cout << "Drawing a rectangle with width " << width << " and height " << height
                  << " at (" << x << ", " << y << ") with color " << color << std::endl;
    }

private:
    int width = 0, height = 0;
};


// 3. Client (客户端)
int main() {
    // 创建原型对象
    Circle* originalCircle = new Circle(10, "Red");
    originalCircle->setPosition(50, 50);

    Rectangle* originalRectangle = new Rectangle(20, 40, "Blue");
    originalRectangle->setPosition(100, 100);

    std::cout << "--- Original Prototypes ---" << std::endl;
    originalCircle->draw();
    originalRectangle->draw();

    // 从原型克隆新对象
    Shape* clonedCircle = originalCircle->clone();
    Shape* clonedRectangle = originalRectangle->clone();

    // 修改克隆对象的状态,不会影响原型
    clonedCircle->setPosition(60, 60);
    clonedCircle->draw();

    clonedRectangle->setPosition(120, 150);
    clonedRectangle->draw();

    std::cout << "\n--- Originals are Unchanged ---" << std::endl;
    originalCircle->draw();
    originalRectangle->draw();


    // 清理内存
    delete originalCircle;
    delete originalRectangle;
    delete clonedCircle;
    delete clonedRectangle;

    return 0;
}

代码讲解

  • Shape 是抽象原型,定义了 clone() 接口。
  • Circle() 和 Rectangle() 是具体原型,它们实现了 clone() 方法。
  • clone() 的实现非常简洁,就是 return new ConcreteType(*this);。它利用了 C++ 的拷贝构造函数来完成成员变量的复制。
  • main 函数作为客户端,先创建好原型实例 originalCircle和 originalRectangle。之后需要新的圆或矩形时,直接调用 clone() 方法,而不是 new Circle(…)。
  • 克隆出的对象 clonedCircle 和原始对象 originalCircle 是两个完全独立的对象,修改一个不会影响另一个。

深拷贝VS浅拷贝

上述例子中,我们所有成员对象都是值类型(int,string),默认函数拷贝类型是浅拷贝,但是如果成员对象中包含指针或引用,那么情况就变得复杂了。

  • 浅拷贝 (Shallow Copy):只复制指针的值,不复制指针所指向的内容。这会导致克隆对象和原型对象共享同一个内部资源。当一个对象修改这个资源时,另一个对象也会受到影响。更严重的是,当其中一个对象被销毁并释放资源时,另一个对象会持有一个悬挂指针,导致程序崩溃。

  • 深拷贝 (Deep Copy):不仅复制指针的值,还会复制指针所指向的内容,为克隆对象分配一块新的内存。这样,克隆对象和原型对象各自拥有独立的内部资源,互不影响。

深拷贝的实现

我们给Shape()类添加一个对象Style(),这个对象Style()通过指针持有。

#include <iostream>
#include <string>
#include <memory> // For smart pointers

// 假设这是一个复杂的样式对象
class Style {
public:
    Style(const std::string& fill) : fillColor(fill) {}
    
    // 拷贝构造
    Style(const Style& other) : fillColor(other.fillColor) {
        std::cout << "Style deep copy constructor called." << std::endl;
    }

    void setFillColor(const std::string& color) {
        fillColor = color;
    }
    
    void print() const {
        std::cout << "Style(FillColor: " << fillColor << ")";
    }

private:
    std::string fillColor;
};

// 原型接口
class Shape {
public:
    // 构造函数,接管 Style 对象所有权
    Shape(Style* style) : style(style) {}

    // 深拷贝的拷贝构造函数
    Shape(const Shape& other) {
        // 深拷贝关键点:为 style 创建一个新的副本
        this->style = new Style(*other.style); 
    }

    virtual ~Shape() {
        delete style; // 需要手动管理内存
    }

    virtual Shape* clone() const = 0;
    
    void showStyle() const {
        style->print();
        std::cout << std::endl;
    }

    void changeFillColor(const std::string& color) {
        style->setFillColor(color);
    }

protected:
    Style* style;
};

// 具体原型
class Circle : public Shape {
public:
    Circle(int r, Style* s) : Shape(s), radius(r) {}
    
    // 拷贝构造函数,调用基类的拷贝构造来处理深拷贝
    Circle(const Circle& other) : Shape(other), radius(other.radius) {}

    Shape* clone() const override {
        return new Circle(*this); // 同样调用拷贝构造
    }
private:
    int radius;
};

int main() {
    // 创建原型
    Circle* originalCircle = new Circle(10, new Style("Red"));
    std::cout << "Original: ";
    originalCircle->showStyle();

    // 克隆
    Shape* clonedCircle = originalCircle->clone();
    std::cout << "Cloned:   ";
    clonedCircle->showStyle();

    // 修改克隆对象的内部状态
    std::cout << "\n--- Modifying cloned object's style ---" << std::endl;
    clonedCircle->changeFillColor("Green");

    std::cout << "Original after modification: ";
    originalCircle->showStyle(); // 原型不受影响
    std::cout << "Cloned after modification:   ";
    clonedCircle->showStyle(); // 克隆对象已改变

    delete originalCircle;
    delete clonedCircle;

    return 0;
}

代码讲解

  • 在 Shape() 的拷贝构造函数中,我们没有简单地写 this->style = other.style; (浅拷贝),而是写了 this->style = new Style(*other.style);。这行代码为新的 Shape 对象创建了一个全新的 Style 对象,内容与原对象的一样,这就是深拷贝。

  • Circle 的 方法依然是clone() ,它会隐式调用Circle 的拷贝构造函数,而 Circle 的拷贝构造函数又会调用基类Shape 的拷贝构造函数,从而正确地完成了Style 对象的深拷贝。

进阶用法

  1. 使用原型管理器/注册表
    • 在实际应用中,我们通常不会让客户端直接持有具体原型类的实例。更好的做法是创建一个管理器(通常是单例),用来存储和管理一系列原型对象。客户端通过一个键(如字符串ID)向管理器请求一个原型,然后克隆它。这有点像工厂方法模式和原型模式的结合:
#include <map>
#include <string>
#include <memory> // 使用智能指针

// Shape, Circle, Rectangle 定义同上,但 clone() 返回智能指针
class Shape {
public:
    virtual ~Shape() = default;
    virtual std::unique_ptr<Shape> clone() const = 0;
    virtual void draw() const = 0;
};

// ... Circle, Rectangle 实现 clone() 返回 std::unique_ptr<Shape>
// 例如:
// std::unique_ptr<Shape> Circle::clone() const {
//     return std::make_unique<Circle>(*this);
// }

class PrototypeFactory {
public:
    static PrototypeFactory& getInstance() {
        static PrototypeFactory instance;
        return instance;
    }

    void registerPrototype(const std::string& key, std::unique_ptr<Shape> prototype) {
        prototypes[key] = std::move(prototype);
    }

    std::unique_ptr<Shape> create(const std::string& key) {
        if (prototypes.find(key) != prototypes.end()) {
            return prototypes[key]->clone();
        }
        return nullptr;
    }

private:
    PrototypeFactory() = default;
    ~PrototypeFactory() = default;
    PrototypeFactory(const PrototypeFactory&) = delete;
    PrototypeFactory& operator=(const PrototypeFactory&) = delete;

    std::map<std::string, std::unique_ptr<Shape>> prototypes;
};

// Client code
int main_with_factory() {
    // 1. 初始化原型并注册到工厂
    PrototypeFactory::getInstance().registerPrototype("BigRedCircle", std::make_unique<Circle>(20, "Red"));
    PrototypeFactory::getInstance().registerPrototype("SmallBlueRect", std::make_unique<Rectangle>(5, 10, "Blue"));

    // 2. 客户端通过 key 来创建对象
    auto shape1 = PrototypeFactory::getInstance().create("BigRedCircle");
    if (shape1) {
        shape1->draw();
    }

    auto shape2 = PrototypeFactory::getInstance().create("SmallBlueRect");
    if (shape2) {
        shape2->setPosition(200, 200); // 修改克隆体
        shape2->draw();
    }

    // 原型本身不会被修改,可以继续用来创建更多对象
    auto anotherCircle = PrototypeFactory::getInstance().create("BigRedCircle");
    if(anotherCircle) {
        anotherCircle->draw(); // 还是原来的20, "Red"
    }

    return 0;
}

优点:

  • 客户端完全解耦:客户端不知道任何具体类的名称,只需要知道注册的键。

  • 动态配置:可以在运行时向管理器添加或删除新的原型,从而改变系统可以创建的对象类型,无需修改代码。

  1. 使用智能指针管理内存
// 在 Shape 基类中
virtual std::unique_ptr<Shape> clone() const = 0;

// 在 Circle 实现中
std::unique_ptr<Shape> Circle::clone() const override {
    return std::make_unique<Circle>(*this);
}

  1. 处理循环引用
    • 当对象图谱中存在循环引用时(A 引用 B,B 又引用 A),进行深拷贝会变得非常棘手,容易导致无限递归和栈溢出。
    • 解决方法是,在执行克隆操作时,维护一个map或unordered_map,记录克隆过的对象,当再次遇到之前克隆过的原始对象时,直接从map<Original*, Cloned*>中获取对应的克隆对象。
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐