c++注意点(10)----设计模式(原型)
创建型模式
原型模式的目的就像文档编辑器中的 "复制粘贴" 功能:
- 当你复制一个文本段落(原型对象)
- 粘贴后得到一个新段落(克隆对象)
- 新段落与原段落内容相同,但可以独立修改
- 这个过程避免了重新输入文本的成本,对应原型模式的核心思想。
如果你有一个对象,你现在希望生成一个与之一模一样的复制品,该如何实现呢?流程一般是:新建一个属于相同类的对象,然后遍历原始对象的所有成员变量,并将成员变量复制到新对象里。但是这种情况有些问题.
寻常做法的问题
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(); // 调用对象自身的克隆方法,无需知道具体类型
}
- 复制逻辑从外部函数转移到了对象内部(
clone()
方法由具体类自己实现)。 - 调用者只需通过
Shape
接口调用clone()
,无需知道对象是Circle
还是Rectangle
。 - 新增任何
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,描述:复制后的风景照描述
优点
- 你可以克隆对象, 而无需与它们所属的具体类相耦合。
- 你可以克隆预生成原型, 避免反复运行初始化代码。
- 你可以更方便地生成复杂对象。
- 你可以用继承以外的方式来处理复杂对象的不同配置。