设计模式 - ProtoType 原型模式
动机
- 在软件系统中,经常面临着"某些结构复杂的对象"的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
- 如何应对这种变化?如何向"客户程序(使用这些对象的程序)"隔离出“这些易变对象”,从而使得依赖这些"易变对象"的客户程序不随着需求改变而改变。
举个例子:
找工作需要简历,以前没有打印机,都是手写很多份简历,且这些简历的内容都一样。但是如果要修改简历中的某项,所有写好的简历都的修改,工作量很大。有了打印设备之后。我们只需要手写一份,然后通过打印机复印多份即可。若要修改,只需修改原始版本,再去复印即可。原始的手写稿就相当与是一个原型,有了原型,就可以通过复印(拷贝)创造出更多的新简历。这就是原型模型的基本思想。
定义
- 使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。 ——《设计模式》GoF
为什么要使用原型模式?
我们使用原型模式是为了创建对象,在以下情况下可以考虑使用原型模式:
- 当我们的对象类型不是一开始就确定的,而是在运行期间确定的话,那么通过利用这个对象克隆出一个新对象会更容易些。
- 获取一个对象在某个状态下的副本
- 处理一些较简单的对象时,且对象间的区别很小,可能就某些属性不同,就可以使用原型模式来创建新的对象。
- 创建对象时,构造函数的参数很多,而自己不 清楚每个参数的意义,就可以使用原型模式创建对象,忽略创建的过程。
由于克隆需要一个原型,而上面的类图中Prototype就这个原型,Prototype定义了克隆自身的Clone接口,由派生类进行实现,而实现原型模式的重点就在于这个Clone接口的实现。ConcretePrototype1类和ConcretePrototype2类继承自Prototype类,并实现Clone接口,实现克隆自身的操作;同时,在ConcretePrototype1类和ConcretePrototype2类中需要重写默认的复制构造函数,供Clone函数调用,Clone就是通过在内部调用重写的复制构造函数实现的。在后续的编码过程中,如果某个类需要实现Clone功能,就只需要继承Prototype类,然后重写自己的默认复制构造函数就好了。
代码实现最简单的原型模式
1 #include <iostream> 2 3 using namespace std; 4 5 //接口 6 class Prototype { 7 public: 8 Prototype() {} 9 virtual ~Prototype() {} 10 virtual Prototype* clone() = 0; 11 }; 12 13 //实现 14 class ConcretePrototype : public Prototype { 15 public: 16 ConcretePrototype() : mCounter(0) {} 17 virtual ~ConcretePrototype() {} 18 19 //重写拷贝构造函数,以供clone函数调用 20 ConcretePrototype(const ConcretePrototype& rhs) { mCounter = rhs.mCounter; } 21 22 //复制自身 23 virtual ConcretePrototype* clone() { 24 //调用拷贝构造函数 25 return new ConcretePrototype(*this); 26 } 27 28 private: 29 int mCounter; 30 }; 31 32 int main() { 33 //生成对象 34 ConcretePrototype* conProA = new ConcretePrototype(); 35 36 //复制自身 37 ConcretePrototype* conProB = conProA->clone(); 38 39 delete conProA; 40 conProA = nullptr; 41 delete conProB; 42 conProB = nullptr; 43 44 return 0; 45 }
与其他创建型模式的比较
工厂方法模式适用于生产较复杂,一个工厂生产单一的一种产品的时候;
抽象工厂模式适用于一个工厂生产多个相互依赖的产品;
建造者模式着重于复杂对象的一步一步创建,组装产品的过程,并在创建的过程中,可以控制每个简单对象的创建;
原型模式则更强调的是从自身复制自己,创建要给和自己一模一样的对象。
要点总结
- 原型模式中具体的创建过程,是由对象本身提供,因此可以很方便的快速的构建新的对象。但是,原型模式的最大缺点是继承原型的子类都要实现Clone操作,这个是很困难的。例如,当所考虑的类已经存在时就难以新增Clone操作。当内部包括一些不支持拷贝或者有循环引用的对象时,实现克隆可能也会很困难。