创建型模式

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

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

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

    寻常做法的问题

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

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

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

    // 假设的复制函数(有问题的写法)
    Shape* copyShape(Shape* shape) {
        // 问题1:必须知道具体类型才能复制
        if (shape->type() == "Circle") {
            Circle* circle = dynamic_cast<Circle*>(shape);
            return new Circle(circle->radius, circle->color); // 依赖Circle类
        } 
        else if (shape->type() == "Rectangle") {
            Rectangle* rect = dynamic_cast<Rectangle*>(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<Shape> clone() const = 0; // 关键:自己复制自己
        virtual void draw() const = 0;
    };
    
    // 具体实现类:自己实现克隆逻辑
    class Circle : public Shape {
    private:
        int radius;
        std::string color;
    public:
        // 克隆自己:创建新Circle并复制状态
        std::unique_ptr<Shape> clone() const override {
            return std::make_unique<Circle>(*this); // 依赖自身,不依赖其他类
        }
        void draw() const override { /* 绘制圆形 */ }
    };
    
    class Rectangle : public Shape {
        // 同理,实现clone()复制自己
    };
    
    // 复制函数:完全依赖接口,不关心具体类型
    std::unique_ptr<Shape> 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<DocumentElement> 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<DocumentElement> clone() const override {
            std::cout << "复制段落(快速操作)" << std::endl;
            // 创建新对象并复制当前状态
            auto copy = std::make_unique<Paragraph>("");
            copy->content = this->content;
            return copy;
        }
        
        void display() const override {
            std::cout << "段落内容:" << content << std::endl;
        }
        
        void setContent(const std::string& content) override {
            this->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<DocumentElement> clone() const override {
            std::cout << "复制图片(快速操作)" << std::endl;
            auto copy = std::make_unique<Image>("", "");
            copy->imagePath = this->imagePath;
            copy->content = this->content;
            return copy;
        }
        
        void display() const override {
            std::cout << "图片:" << imagePath << ",描述:" << content << std::endl;
        }
        
        void setContent(const std::string& content) override {
            this->content = content;
        }
    };
    
    // 文档编辑器:使用原型模式复制元素
    class DocumentEditor {
    public:
        // 复制任意文档元素
        static std::unique_ptr<DocumentElement> duplicate(const DocumentElement& prototype) {
            return prototype.clone(); // 调用原型的克隆方法
        }
    };
    
    int main() {
        // 1. 创建原始元素(成本高)
        auto originalPara = std::make_unique<Paragraph>("这是第一段文字");
        auto originalImage = std::make_unique<Image>("photo.jpg", "风景照");
        
        // 2. 复制元素(通过原型模式,成本低)
        auto copiedPara = DocumentEditor::duplicate(*originalPara);
        auto copiedImage = DocumentEditor::duplicate(*originalImage);
        
        // 3. 修改复制后的元素(不影响原始元素)
        copiedPara->setContent("这是复制后修改的段落");
        copiedImage->setContent("复制后的风景照描述");
        
        // 4. 展示结果
        std::cout << "\n原始元素:" << std::endl;
        originalPara->display();
        originalImage->display();
        
        std::cout << "\n复制后的元素:" << std::endl;
        copiedPara->display();
        copiedImage->display();
        
        return 0;
    }
    

    结果为:

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

    优点

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

    Agent 垂直技术社区,欢迎活跃、内容共建。

    更多推荐