简单介绍
原型模式是一种创建型设计模式 | 它使你能够复制已有对象,客户端不需要知道要复制的对象是哪个类的实例,只需通过原型工厂获取该对象的副本。 以后需要更改具体的类或添加新的原型类,客户端代码无需改变,只需修改原型工厂即可 。
基础理解
Q:为什么使用原型模式
A:如果你有一个对象, 并希望生成与其完全相同的一个复制品。如果直接复制:
- 对方可能有私有成员变量(你无法访问私有)
- 不知道对方的具体类(可能使用父类接口,但我们需要复制的是具体子类)
解决方案
- 那我们在外部无法克隆,便可以想想在类的内部设置一个通用的克隆接口。对象可以访问同类对象的私有
- 克隆返回的对象的配置要与预先的配置相同。甚至有时候当构造函数变量很多几十个,克隆可以完全代替子类构造函数
UML 图
原型注册表 (Prototype Registry) 最简单的注册表原型是一个 名称 → 原型的哈希表。
实现步骤
- 创建原型接口, 并在其中声明 克隆方法。 如果你已有类层次结构, 则只需在其所有类中添加该方法即可。
- 原型类必须另行定义一个以该类对象为参数的构造函数。如果你需要修改子类,贼需要调用父类构造函数,让父类复制变量与子类保持一致。
- 克隆方法通常只有一行代码
newConcretePrototype1(*this);
每个类都必须显式重写克隆方法并使用自身类名调用 new运算符。 - 还可以创建一个原型注册表, 用于存储常用原型。将对子类构造函数的直接调用替换为对原型注册表的调用。
#include <iostream>
#include <string>
#include <unordered_map>
using std::string;
enum Type //枚举类
{
PROTOTYPE_1 = 0,
PROTOTYPE_2
};
//抽象原型类
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 << "从 " << prototype_name_ << " 中调用 Method 方法,字段值为:" << prototype_field << std::endl;
}
};
//具体原型类1
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)
{
}
Prototype *Clone() const override
{
return new ConcretePrototype1(*this);
}
};
//具体原型类2
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);
}
};
//原型注册表
class PrototypeFactory
{
private:
std::unordered_map<Type, Prototype *, std::hash<int>> prototypes_;
public:
PrototypeFactory()
{
prototypes_[Type::PROTOTYPE_1] = new ConcretePrototype1("原型 1", 50.f);
prototypes_[Type::PROTOTYPE_2] = new ConcretePrototype2("原型 2", 60.f);
}
~PrototypeFactory()
{
for (auto it = prototypes_.begin(); it != prototypes_.end(); ++it)
{
delete it->second;
}
prototypes_.clear();
}
Prototype *CreatePrototype(Type type)
{
return prototypes_[type]->Clone();
}
};
void Client(PrototypeFactory &prototype_factory)
{
std::cout << "创建原型 1\n";
Prototype *prototype = prototype_factory.CreatePrototype(Type::PROTOTYPE_1);
prototype->Method(90);
delete prototype;
std::cout << "\n";
std::cout << "创建原型 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;
}
应用场景
你需要复制一些对象, 且独立于这些对象所属的具体类,减少耦合
通常出现在代码需要处理第三方代码通过接口传递过来的对象时。 即使不考虑代码耦合的情况, 你的代码也不能依赖这些对象所属的具体类: 可能人家更改了一下,你就崩了。因为你的客户端也需要更改。如我开头所说的一样
子类的区别仅在于其对象的初始化方式, 那么你可以使用该模式来减少子类的数量。
客户端不必根据需求对子类进行实例化, 只需找到合适的原型并对其进行克隆即可。
与其他模式的关系
-
在许多设计工作的初期都会使用简单工厂模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。
-
抽象工厂模式通常基于一组简单工厂, 但你也可以使用原型模式来生成这些类的方法。(在工厂类中添加clone 方法,动态地创建具体的工厂类,而不需要使用new 创建)
-
原型可用于保存命令模式的历史记录。保存历史记录,可以在需要时重新执行或撤销先前执行的命令。
-
大量使用组合模式和装饰模式的设计通常可从对于原型的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。
-
原型并不基于继承, 因此没有继承的缺点。 另一方面, 原型需要对被复制对象进行复杂的初始化。 简单工厂基于继承, 但是它不需要初始化步骤。
-
有时候原型可以作为备忘录模式的一个简化版本。当对象的状态相对简单,且不需要频繁保存和恢复时,原型模式是一个更简洁的方案。
代码示例
优缺点
优点 | 缺点 |
---|---|
你可以克隆对象, 而无需与它们所属的具体类相耦合 | 克隆包含循环引用的复杂对象可能会非常麻烦。 |
你可以克隆预生成原型, 避免反复运行初始化代码。 | |
你可以更方便地生成复杂对象。 | |
你可以用继承以外的方式来处理复杂对象的不同配置。 |
如果有错还望指正。有什么建议也可以留言。
你的赞是我的莫大动力。谢谢大家。
参考文档