07. 原型模式
一、原型模式
在设计模式中也存在一个类似的模式,可以通过一个原型对象克隆出多个一模一样的对象,该模式被称为 原型模式。
原型模式(Prototype Pattern)是一种创建型设计模式,它允许通过复制(或克隆)一个已经存在的对象来创建新的对象,而无需重新实例化或重新配置。在使用原型模式时,需要首先创建一个原型对象,再通过复制这个原型对象来创建更多同类型的对象。原型模式是一种 “另类” 的创建型模式,创建克隆对象的工厂就是原型类自身,工厂方法由克隆方法来实现。
在原型模式中,有几个关键的角色:
- 抽象原型(Prototype):这是一个抽象角色,通常由一个接口或抽象类实现,给出所有的具体原型类所需的接口。
- 具体原型(Concrete Prototype):被复制的对象,需要实现抽象的原型角色所要求的接口。
总的来说,原型模式通过复制原型对象来创建新的对象,从而提高了创建对象的效率和性能。
二、C++实现原型模式
原型类,通常由一个接口或抽象类实现,给出所有的具体原型类所需的接口。
// 原型类,声明了克隆方法
class Prototype
{
public:
virtual Prototype * clone(void) = 0; // 克隆方法,返回原型对象的副本
};
通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员变量相同。
// 具体原型类,实现了克隆方法和示例操作
class Sheep : public Prototype
{
private:
std::string name;
public:
Sheep(void);
Sheep(std::string name);
Sheep * clone(void) override; // 实现克隆方法,返回具体原型对象的副本
void bark(void); // 叫声
std::string getName(void);
void setName(std::string name);
};
Sheep::Sheep(void) {}
Sheep::Sheep(std::string name) : name(name) {}
Sheep * Sheep::clone(void)
{
return new Sheep(*this);
}
void Sheep::bark(void)
{
std::cout << getName() + "在叫" << std::endl;
}
std::string Sheep::getName(void)
{
return name;
}
void Sheep::setName(std::string name)
{
this->name = name;
}
main() 函数:
int main(void)
{
// 创建原型对象
ConcretePrototype * prototype = new ConcretePrototype("Initial Data");
// 克隆原型对象
ConcretePrototype * cloned = prototype->Clone();
// 显示原型对象和克隆对象的数据(应该是相同的)
cout << "Prototype: " << endl;
prototype->Display();
cout << "Cloned: " << endl;
cloned->Display();
// 清理内存
delete prototype;
delete cloned;
return 0;
}
三、深浅拷贝问题
C++ 默认提供了一个拷贝构造器。但默认提供的拷贝构造器实现按成员拷贝,这意味着在拷贝的对象和原来对象之间是共享指针的。但克隆一个结构复杂的原型通常需要深拷贝,这是因为复制对象和原对象之间必须相互独立,因此必须保证克隆对象的构件也是对原型的构件的拷贝。
具体原型类,实现了克隆方法,这里由于原型类中没有指针或者引用类型变量,因此在实现克隆方法时,采用的是默认拷贝构造函数。
// 具体原型类,实现了克隆方法和示例操作
class School : public Prototype
{
private:
std::string name;
std::string address;
public:
School(void);
School(std::string name, std::string address);
void showInfo(void);
School * clone(void) override;
std::string getName(void);
void setName(std::string name);
std::string getAddress(void);
void setAddress(std::string address);
};
School::School(void) : name(""), address("") {}
School::School(std::string name, std::string address) : name(name), address(address) {}
void School::showInfo(void)
{
std::cout << "School{name: " << name << ", address: " << address << "}" << std::endl;
}
School * School::clone(void)
{
return new School(*this);
}
std::string School::getName(void)
{
return name;
}
void School::setName(std::string name)
{
this->name = name;
}
std::string School::getAddress(void)
{
return address;
}
void School::setAddress(std::string address)
{
this->address = address;
}
具体原型类,实现了克隆方法,这里由于原型类中有指针或者引用类型变量,因此在实现克隆方法时,需要重写拷贝构造函数,实现深拷贝。
// 具体原型类,实现了克隆方法和示例操作
class Student : public Prototype
{
private:
std::string name;
int age;
School *school;
public:
Student(void);
Student(std::string name, int age);
~Student(void);
void showInfo(void);
Student * clone(void) override;
std::string getName(void);
void setName(std::string name);
int getAge(void);
void setAge(int age);
School * getSchool(void);
void setSchool(School *school);
};
Student::Student(void) : name(""), age(0), school(new School()) {}
Student::Student(std::string name, int age) : name(name), age(age) {}
Student::~Student(void)
{
if (school != nullptr)
{
delete school;
}
}
void Student::showInfo(void)
{
std::cout << "Studet{name: " << name << ", age: " << age << ", school: " << school->getName() << "}" << std::endl;
}
Student * Student::clone(void)
{
Student *student = new Student(*this);
if (school != nullptr)
{
student->setSchool(school->clone());
}
return student;
}
std::string Student::getName(void)
{
return name;
}
void Student::setName(std::string name)
{
this->name = name;
}
int Student::getAge(void)
{
return age;
}
void Student::setAge(int age)
{
this->age = age;
}
School * Student::getSchool(void)
{
return school;
}
void Student::setSchool(School *school)
{
this->school = school;
}
main() 函数:
#include <iostream>
int main(void)
{
// 创建原型对象
School * school = new School("友枝小学", "京都府京都中京区");
Student * prototype = new Student("木之本樱", 14);
prototype->setSchool(school);
// 克隆原型对象
Student * cloned = prototype->clone();
// 显示原型对象和克隆对象的数据(应该是相同的)
std::cout << "Prototype: " << std::endl;
prototype->showInfo();
std::cout << "Cloned: " << std::endl;
cloned->showInfo();
// 修改克隆对象的数据
cloned->setAge(12);
cloned->getSchool()->setName("友枝中学");
// 输出原对象的数据和克隆对象的数据
std::cout << "修改克隆对象的数据后: " << std::endl;
prototype->showInfo();
cloned->showInfo();
delete prototype;
delete cloned;
return 0;
}
四、原型模式的总结
4.1、原型模式的优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
- 扩展性较好。由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少具体原型类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构。工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样。原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态。使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,例如恢复到某一历史状态,可辅助实现撤销操作。
4.2、原型模式的缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部。当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
4.3、原型模式的适用场景
- 创建新对象成本较大(例如初始化需要占用较长的时间,占用太多的CPU资源或网络资源)。新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态。通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。