> 技术文档 > c++注意点(10)----设计模式(原型)

c++注意点(10)----设计模式(原型)


创建型模式

原型模式的目的就像文档编辑器中的 \"复制粘贴\" 功能

  • 当你复制一个文本段落(原型对象
  • 粘贴后得到一个新段落(克隆对象)
  • 新段落与原段落内容相同,但可以独立修改
  • 这个过程避免了重新输入文本的成本,对应原型模式的核心思想。

        如果你有一个对象,你现在希望生成一个与之一模一样的复制品,该如何实现呢?流程一般是:新建一个属于相同类的对象,然后遍历原始对象的所有成员变量,并将成员变量复制到新对象里。但是这种情况有些问题.

    寻常做法的问题

            1.有些对象是有私有变量的,并不是所有的变量你都能访问到。

            2.即使你在该对象的类的内部实现一个复制操作,但是你仍然必须知道对象所属的类。所以你的代码必须依赖该类。即便你能够接受这样的要求,那也有别的问题。比如说你只知道对象所实现的接口,并不知道其所属的类。举例子:

            假设你在开发一个图形编辑器,支持多种形状(圆形、矩形、三角形等),这些形状都实现了Shape接口(包含draw()clone()等方法)。

    // 假设的复制函数(有问题的写法)Shape* copyShape(Shape* shape) { // 问题1:必须知道具体类型才能复制 if (shape->type() == \"Circle\") { Circle* circle = dynamic_cast(shape); return new Circle(circle->radius, circle->color); // 依赖Circle类 } else if (shape->type() == \"Rectangle\") { Rectangle* rect = dynamic_cast(shape); return new Rectangle(rect->width, rect->height); // 依赖Rectangle类 } // ... 其他形状的判断 return nullptr;}

            问题1:copyShape函数必须知道所有Shape的派生类(Circle、Rectangle等),一旦新增形状(如Triangle),必须修改这个函数,违反 “开闭原则”。

            问题2:如果shape是一个仅通过接口传递的对象(比如从外部库获取,你不知道它的具体类型),dynamic_cast和类型判断会失效,根本无法创建复制品。简单点说就是,除了Circle,Rectangle是你知道的外,如果是别的传进来,你就无法做出判断了。

    解决方法

            使用原型模式,原型模式将克隆过程委派给被克隆的实际对象。 模式为所有支持克隆的对象声明了一个通用接口, 该接口让你能够克隆对象, 同时又无需将代码和对象所属类耦合。 通常情况下, 这样的接口中仅包含一个克隆方法。举例子:

    // 抽象接口:定义克隆方法class Shape {public: virtual ~Shape() = default; virtual std::unique_ptr clone() const = 0; // 关键:自己复制自己 virtual void draw() const = 0;};// 具体实现类:自己实现克隆逻辑class Circle : public Shape {private: int radius; std::string color;public: // 克隆自己:创建新Circle并复制状态 std::unique_ptr clone() const override { return std::make_unique(*this); // 依赖自身,不依赖其他类 } void draw() const override { /* 绘制圆形 */ }};class Rectangle : public Shape { // 同理,实现clone()复制自己};// 复制函数:完全依赖接口,不关心具体类型std::unique_ptr copyShape(const Shape& shape) { return shape.clone(); // 调用对象自身的克隆方法,无需知道具体类型}
    1. 复制逻辑从外部函数转移到了对象内部(clone()方法由具体类自己实现)。
    2. 调用者只需通过Shape接口调用clone(),无需知道对象是Circle还是Rectangle
    3. 新增任何Shape派生类时,copyShape函数无需修改,只需该类实现clone()即可。

     再给一个例子,比如做文本编辑器。

    // 抽象原型类:定义克隆接口class DocumentElement {public: virtual ~DocumentElement() = default; virtual std::unique_ptr clone() const = 0; virtual void display() const = 0; virtual void setContent(const std::string& content) = 0;protected: std::string content; // 元素内容};// 具体原型1:文本段落class Paragraph : public DocumentElement {public: Paragraph(const std::string& text) { // 模拟复杂初始化过程(可能来自数据库或网络) std::cout << \"创建新段落(耗时操作)\" << std::endl; content = text; } // 克隆方法:通过复制当前对象创建新实例 std::unique_ptr clone() const override { std::cout << \"复制段落(快速操作)\" << std::endl; // 创建新对象并复制当前状态 auto copy = std::make_unique(\"\"); copy->content = this->content; return copy; } void display() const override { std::cout << \"段落内容:\" << content <content = content; }};// 具体原型2:图片class Image : public DocumentElement {private: std::string imagePath; // 图片路径 public: Image(const std::string& path, const std::string& desc) { // 模拟加载图片的耗时操作 std::cout << \"加载图片 \" << path << \"(耗时操作)\" << std::endl; imagePath = path; content = desc; } std::unique_ptr clone() const override { std::cout << \"复制图片(快速操作)\" << std::endl; auto copy = std::make_unique(\"\", \"\"); copy->imagePath = this->imagePath; copy->content = this->content; return copy; } void display() const override { std::cout << \"图片:\" << imagePath << \",描述:\" << content <content = content; }};// 文档编辑器:使用原型模式复制元素class DocumentEditor {public: // 复制任意文档元素 static std::unique_ptr duplicate(const DocumentElement& prototype) { return prototype.clone(); // 调用原型的克隆方法 }};int main() { // 1. 创建原始元素(成本高) auto originalPara = std::make_unique(\"这是第一段文字\"); auto originalImage = std::make_unique(\"photo.jpg\", \"风景照\"); // 2. 复制元素(通过原型模式,成本低) auto copiedPara = DocumentEditor::duplicate(*originalPara); auto copiedImage = DocumentEditor::duplicate(*originalImage); // 3. 修改复制后的元素(不影响原始元素) copiedPara->setContent(\"这是复制后修改的段落\"); copiedImage->setContent(\"复制后的风景照描述\"); // 4. 展示结果 std::cout << \"\\n原始元素:\" <display(); originalImage->display(); std::cout << \"\\n复制后的元素:\" <display(); copiedImage->display(); return 0;}

    结果为:

    创建新段落(耗时操作)加载图片 photo.jpg(耗时操作)复制段落(快速操作)创建新段落(耗时操作)复制图片(快速操作)加载图片 (耗时操作)原始元素:段落内容:这是第一段文字图片:photo.jpg,描述:风景照复制后的元素:段落内容:这是复制后修改的段落图片:photo.jpg,描述:复制后的风景照描述

    优点

    1. 你可以克隆对象, 而无需与它们所属的具体类相耦合。
    2.  你可以克隆预生成原型, 避免反复运行初始化代码。
    3. 你可以更方便地生成复杂对象。
    4.  你可以用继承以外的方式来处理复杂对象的不同配置。