一、什么是原型模式?
关于原型模式的介绍,网上已有不少好的文章进行讲解,图文并茂,生动形象。在此引用两篇非常不错的介绍,推荐大家:
1、 C++设计模式——原型模式 - Ring_1992 - 博客园 (cnblogs.com)
2、 原型设计模式 (refactoringguru.cn)
拿最简单的例子来说,一个考试系统中,每个考生的试卷内容相同,但是题目的次序,甚至选项的次序不同。通过一份原始的试卷A,能拷贝生成不同的试卷B、C、D。这就是原型模式的一个例子。类似的还有细胞的有丝分裂,由一个细胞分裂成两个相同的细胞,等等。它们都是通过现有的东西,再复制出另一个,这就是原型模式。
如果从实现的方面来说明原型模式的话,它的UML图如下:
由于克隆需要一个原型,而上面的类图中Prototype
就这个原型,Prototype定义了克隆自身的Clone
接口,由派生类进行实现,而实现原型模式的重点就在于这个Clone
接口的实现。
ConcretePrototype1
类和ConcretePrototype2
类继承自Prototype
类,并实现Clone
接口,实现克隆自身的操作;比如考试系统中的试题可以实现Clone
接口,在Clone
中将选项的顺序打乱,而不改变选项内容。
同时,在ConcretePrototype1
类和ConcretePrototype2
类中需要重写拷贝构造函数
,供Clone
函数调用,Clone
就是通过在内部调用重写拷贝构造函数
实现的。在后续的编码过程中,如果某个类需要实现Clone
功能,就只需要继承Prototype
类,然后重写自己的拷贝构造函数
就好了。
二、它有哪些优点?缺点?
优点
当我们的对象类型不是开始就能确定的,而这个类型是在运行期确定的话,那么我们通过克隆已有的对象比直接创建一个新的对象要更容易一些;
有的时候,我们需要一个对象在某个状态下的副本,此时,我们使用原型模式是最好的选择;例如:一个对象,经过一段处理之后,其内部的状态发生了变化(私有成员发生改变);这个时候,我们需要一个这个状态的副本,如果直接new一个新的对象的话,但是它的状态是不对的,此时,可以使用原型模式,将原来的对象拷贝一个出来,这个对象就和之前的对象是完全一致的了;
有的时候,创建对象时,构造函数的参数很多,而自己又不完全的知道每个参数的意义,就可以使用原型模式来创建一个新的对象,不必去理会创建的过程。
缺点
继承原型的子类都要实现Clone操作
clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
三、举例
1、示例程序
using std::string;
// Prototype Design Pattern
// 拷贝已有对象而不用依赖它们的构造函数。
enum Type {
PROTOTYPE_1 = 0,
PROTOTYPE_2
};
/**
* 接口类提供克隆功能,提供Method方法打印不同类型的输出值打印。
*/
class Prototype {
protected:
string prototype_name_;
float prototype_field_;
public:
Prototype() {}
Prototype(string prototype_name)
: prototype_name_(prototype_name) {
}
virtual ~Prototype() {}
virtual Prototype *Clone() const = 0;
virtual void Method(float prototype_field) {
this->prototype_field_ = prototype_field;
std::cout << "Call Method from " << prototype_name_ << " with field : " << prototype_field << std::endl;
}
};
/**
* ConcretePrototype1是Prototype的一个子类,实现了克隆方法。
* 在这个例子中原型类的所有数据成员都在堆栈中,所以没有实现拷贝构造函数。
* 如果你有指针成员,如:String* name,你需要重写拷贝构造函数,实现深拷贝。
*/
class ConcretePrototype1 : public Prototype {
private:
float concrete_prototype_field1_;
public:
ConcretePrototype1(string prototype_name, float concrete_prototype_field)
: Prototype(prototype_name), concrete_prototype_field1_(concrete_prototype_field) {
}
/**
* 注意,Clone方法返回一个指针, 指向新开辟的空间,里面存入了ConcretePrototype1的复制品。
* 因此,client(调用clone方法)有责任释放那段内存。
* 如果你有智能指针的知识,你可以在这里使用unique_pointer。
*/
Prototype *Clone() const override {
return new ConcretePrototype1(*this);
}
};
class ConcretePrototype2 : public Prototype {
private:
float concrete_prototype_field2_;
public:
ConcretePrototype2(string prototype_name, float concrete_prototype_field)
: Prototype(prototype_name), concrete_prototype_field2_(concrete_prototype_field) {
}
Prototype *Clone() const override {
return new ConcretePrototype2(*this);
}
};
/**
* 在PrototypeFactory中, 有两个已经实例化的原型。你可以继续克隆出其它成员。
*/
class PrototypeFactory {
private:
std::unordered_map<Type, Prototype *, std::hash<int>> prototypes_;
public:
PrototypeFactory() {
prototypes_[Type::PROTOTYPE_1] = new ConcretePrototype1("PROTOTYPE_1 ", 50.f);
prototypes_[Type::PROTOTYPE_2] = new ConcretePrototype2("PROTOTYPE_2 ", 60.f);
}
/**
* 一定要小心内存分配,最好在这使用智能指针。
*/
~PrototypeFactory() {
delete prototypes_[Type::PROTOTYPE_1];
delete prototypes_[Type::PROTOTYPE_2];
}
/**
* 这里你只需要指定你想克隆哪个原型,就可以克隆相应的对象。
*/
Prototype *CreatePrototype(Type type) {
return prototypes_[type]->Clone();
}
};
void Client(PrototypeFactory &prototype_factory) {
std::cout << "Let's create a Prototype 1\n";
Prototype *prototype = prototype_factory.CreatePrototype(Type::PROTOTYPE_1);
prototype->Method(90);
delete prototype;
std::cout << "\n";
std::cout << "Let's create a Prototype 2 \n";
prototype = prototype_factory.CreatePrototype(Type::PROTOTYPE_2);
prototype->Method(10);
delete prototype;
}
int main() {
PrototypeFactory *prototype_factory = new PrototypeFactory();
Client(*prototype_factory);
delete prototype_factory;
return 0;
}
2、试题复制
#include <iostream>
#include <string>
#include <vector>
#include <set>
#include <algorithm>
class ProtoType {
public:
virtual ~ProtoType() {};
virtual ProtoType* Clone() = 0;
virtual void Show() = 0;
};
class QuestionA : public ProtoType {
public:
QuestionA() : problem("waht's your name?"), options({"dongdong", "youyou", "tutu", "beibei"}) {}
~QuestionA() {}
ProtoType* Clone() override {
ProtoType* q = new QuestionA(*this);
// 改变选项顺序
QuestionA* ptr = dynamic_cast<QuestionA*>(q);
random_shuffle(ptr->options.begin(), ptr->options.end());
return q;
}
void Show() override {
std::cout << problem << std::endl;
printf("A:%s \t B:%s \t C:%s \t D:%s \n",
options[0].c_str(), options[1].c_str(), options[2].c_str(), options[3].c_str());
}
private:
std::string problem;
std::vector<std::string> options;
};
int main() {
QuestionA q_a;
ProtoType* q_b = q_a.Clone();
q_a.Show();
q_b->Show();
delete q_b;
return 0;
}