1.定义
使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
——《设计模式》GoF
- 通过给出一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的办法创建出更多的同类型对象。
- 动机: 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;
- 由于需求的变化,这些对象经常面临着剧烈的变化(如更换类型),但是它们却拥有比较稳定一致的接口(共同的父类)。
- 如何应对这种变化?
- 如何向客户端程序隔离出这些易变对象,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?
2.结构
- 客户(Client)角色:客户类提出创建对象的请求。
- 抽象原型(Prototype)角色:这是一个抽象角色,通常由一个用于复制/克隆的接口或抽象类实现。此角色给出所有的具体原型类所需的接口。在C#中,抽象原型角色通常实现了ICloneable接口(自定义一个方法如:DeepCopy更好)。
- 具体原型(Concrete Prototype)角色:被复制的对象。此角色需要实现抽象原型角色所要求的复制/克隆接口。
3.深浅拷贝
学原型模式应该复习一下深浅拷贝。以书上的习题简单阐述一下深浅拷贝的区别
问题描述:为某销售管理系统设计并实现一个客户类Customer,在客户类中包含一个名为客户地址的成员变量,客户地址的类型为Address,用浅克隆和深克隆分别实现Customer对象的复制,并比较这两种克隆方式的异同。
//customer实体类
//省略了构造函数,还有一系列get/set方法
package PrototypePattern;
public class Customer implements Cloneable{
private String name;
private Address address;
//继承Cloneable接口,重写clone方法实现拷贝
@Override
protected Object clone(){
Customer p=null;
try {
p = (Customer) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
//测试类
public class test {
public static void main(String[] args) {
//创建一个customer对象
Customer customer = new Customer("zhangsan", new Address("CN", "shandong", "qingdao"));
//利用Cloneable克隆一个对象
Customer cloneCustomer = (Customer) customer.clone();
System.out.println(cloneCustomer);
customer.setAddress(new Address("CN", "shandong", "jinan"));
System.out.println(customer);
System.out.println(cloneCustomer);
}
}
执行结果:
Customer [name=zhangsan, address=Address [Country=CN, Province=shandong, City=qingdao]]
Customer [name=zhangsan, address=Address [Country=CN, Province=shandong, City=jinan]]
Customer [name=zhangsan, address=Address [Country=CN, Province=shandong, City=qingdao]]
可以看出,cloneable接口实现的是浅克隆,当原型对象customer被复制时,它的引用数据类型Address并没有被复制,而是指向原型对象的指针。当修改其中的内容时,拷贝对象的指针(也就是地址)还是指向同一块内容,但不是我们想要的内容了。
即引用类型的成员变量并没有复制。
简单的总结一下深浅克隆的区别:
对于基本数据类型(如int、double等),深浅克隆使用的不是一个地址。
对于引用数据类型(引用数据类型包括类、接口、数组等复杂数据类型),深克隆中复制的对象与原型对象用的不是同一个地址,浅克隆用的是同一个地址。
如果想要实现深克隆,就要再每一层对象对应的类支持深克隆,上述只有一个Address类需要实现,如果类中有多个引用数据类型,那实现起来就比较复杂了,并且类已写好,再去修改,实际上违背了开闭原则。
4.原型模式的优缺点
- 原型模式的优点:
- 简化对象的创建过程,通过复制一个已有对象实例可以提高新实例的创建效率
- 扩展性好
- 提供了简化的创建结构,原型模式中的产品的复制是通过封装在原型类中的克隆方法实现的,无需专门的工厂类来创建产品
- 可以通过深克隆的方式保存对象的状态,使用原型模式将对象复制一份并其状态保存起来,以便在需要的时候使用,可辅助实现撤销操作
- 原型模式的缺点:
- 需要为每一个类准备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有类进行改造时,需要修改原代码,违背了开闭原则
- 在实现深克隆时需要写较复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类必须支持深克隆,实现起来麻烦
5.适用环境
- 创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量修改
- 系统要保存对象的状态,而对象的状态变化很小
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更方便