【设计模式 - 创建型模式】4. 原型模式
一、简介
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
原型模式(Prototype)也正是提供了自我复制的功能, 就是说新对象的创建可以通过已有对象进行创建。在 C++中,拷贝构造函数曾经是很对程序员的噩梦,浅层拷贝和深层拷贝的魔魇也是很多程序员在面试时候的快餐和系统崩溃时候的根源之一。
在 GOF 的《设计模式:可复用面向对象软件的基础》中是这样说的:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。这这个定义中,最重要的一个词是“拷贝”,也就是口头上的复制,而这个拷贝,也就是原型模式的精髓所在。其 UML 类图如下:
由于克隆需要一个原型,而上面的类图中 Prototype 就这个原型,Prototype 定义了克隆自身的 Clone 接口,由派生类进行实现,而实现原型模式的重点就在于这个 Clone 接口的实现。ConcretePrototype1 类和 ConcretePrototype2类继承自 Prototype 类,并实现 Clone 接口,实现克隆自身的操作;同时,在 ConcretePrototype1 类和 ConcretePrototype2 类中需要重写默认的拷贝构造函数,供 Clone 函数调用,Clone 就是通过在内部调用重写的拷贝构造函数实现的。
二、使用场合
原型模式和建造者模式、工厂方法模式一样,都属于创建型模式的一种。简单的来说,我们使用原型模式,就是为了创建对象。不过,适合原型模式的最好选择如下:
-
当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易一些;
-
有的时候,我们需要一个对象在某个状态下的副本,此时,我们使用原型模式是最好的选择;例如:一个对象,经过一段处理之后,其内部的状态发生了变化;这个时候,我们需要一个这个状态的副本,如果直接 new 一个新的对象的话,但是它的状态是不对的,而且开销要大的多。此时,可以使用原型模式,将原来的对象拷贝一个出来,这个对象就和之前的对象是完全一致的了;
-
当我们处理一些比较简单的对象时,并且对象之间的区别很小,可能就几个属性不同而已,那么就可以使用原型模式来完成,省去了创建对象时的麻烦了;
-
有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
适当的时候考虑一下原型模式,能减少对应的工作量,减少程序的复杂度,提高效率。
三、代码实现
#include <iostream>
#include <string>
using namespace std;
// 原型基类
class Prototype
{
public:
// 默认构造函数
Prototype() {str = "";}
// 拷贝构造函数
Prototype(string s) {str = s;}
// Clone接口
virtual Prototype *clone() = 0;
void show() {cout << str << endl;}
private:
string str;
};
// 具体基类1
class ConcretePrototype1 : public Prototype
{
public:
// 默认构造函数
ConcretePrototype1(){}
// 重写拷贝构造函数
ConcretePrototype1(string s): Prototype(s){}
// 重写Clone接口
virtual Prototype *clone()
{
ConcretePrototype1 *p = new ConcretePrototype1();
*p = *this;
return p;
}
};
// 具体基类2
class ConcretePrototype2 : public Prototype
{
public:
// 默认构造函数
ConcretePrototype2(){}
// 重写拷贝构造函数
ConcretePrototype2(string s): Prototype(s) {}
// 重写Clone接口
virtual Prototype *clone()
{
ConcretePrototype2 *p = new ConcretePrototype2();
*p = *this;
return p;
}
};
int main()
{
ConcretePrototype1 *test = new ConcretePrototype1("小李");
ConcretePrototype2 *test2 = (ConcretePrototype2 *)test->clone();
test->show();
test2->show();
return 0;
}
输出如下:
小李
小李
四、具体实现
代码如下:
#include <iostream>
#include <string>
using namespace std;
// 个人简历类
class Resume
{
public:
// 自定义构造函数
Resume()
{
name = "Tom";
sex = "man";
timeArea = "2007-2010";
company = "research";
}
// 设置个人信息
void setPersonalInfo(string s, string a)
{
sex = s;
age = a;
}
// 设置工作经历
void setWorkExperience(string t, string c)
{
timeArea = t;
company = c;
}
// 显示简历信息
void display()
{
cout << name << " " << sex << " " << age << endl;
cout << "work: " << timeArea << " " << company << endl << endl;
}
// clone接口
Resume *clone()
{
Resume *b;
b = new Resume();
b->setPersonalInfo(sex, age);
b->setWorkExperience(timeArea, company);
return b;
}
private:
string name, sex, age, timeArea, company;
};
int main()
{
Resume *r = new Resume();
r->display();
r->setWorkExperience("2003-2007", "benke");
r->display();
Resume *r2 = r->clone();
r2->display()
return 0;
}
输出如下:
Tom man
work: 2007-2010 research
Tom man
work: 2003-2007 benke
Tom man
work: 2003-2007 benke
可以看到对象 r 经过一段处理之后,其信息发生了变化;这个时候,我们需要一个这个状态的副本,如果直接 new 一个新的对象的话,但是它的状态是不对的,此时就可以使用原型模式,将改变后的对象拷贝一个出来。
参考:
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!