原型模式属于创建对象类型模式,获取一个类的对象时不是每次从头创建一个新对象,而是从先创建类的一个对象作为原型,每次获取对象时从已创建好的对象复制一份。在Java 中,实现原型模式,一般通过对象克隆技术实现。
为什么创建对象时是对象复制而不是通过构造方法创建一个对象呢?哪个方式更简单?大部分情况下不需要复制对象,但是在企业应用开发中对性能要求比较苛刻时,创建一个对象需要调用非常耗时的操作,比如网络请求、读数据库或者读取磁盘文件,并存在大量请求创建该对象,为了提升性能避免重复创建,这时可以用原型模式来解决了。
java 提供了 Cloneable
接口来标记对象是否允许克隆,此接口是标记接口没有包含任何方法。当一个类支持克隆时只需要 implements
接口 Cloneable
,同时,在执行克隆对象的方法中调用 super.clone()
。
对原对象调用 super.clone()
方法实现的拷贝是浅拷贝,只会拷贝原对象的原始类型的字段的值,当一个字段是引用类型时,拷贝的只是引用,无论修改原对象还是克隆对象的引用类型字段时都会互相影响,会造成意想不到的行为发生。在此情况下,应该使用深拷贝来解决问题,深拷贝会对引用类型字段申请内存空间来存储从引用类型字段中拷贝的数据。
浅拷贝和深拷贝的差异:
上图中,浅拷贝的引用同时引用一个对象,深拷贝指向不同的对象。
当实现原型模式时,我们是用浅拷贝还是深拷贝呢? 没用硬性规定,按照实际需求选择,如果一个对象只有原型类型字段或者是不可变的对象,此时用浅拷贝。当对象有引用其他可变对象时,可以选择深或浅拷贝。一个引用的对象在运行时可以保证不被修改,应该避免深拷贝。
当实现深拷贝时,类的所有引用类型字段都要重写 Object.clone
方法,在克隆的时候递归调用。还有一个可选方案,利用序列化和反序列。
要了解原型模式是如何工作的,现举例有一家内容生产公司,该公司雇用供应商为客户编写内容。 对于分配给卖方的每个项目,人力资源部门都会为卖方提供条款和条件以及保密协议,卖方必须在开始工作之前接受这些协议。 所有供应商的协议内容均相同,并且人力资源员工只需在向供应商发送协议之前填写供应商名称。 假设协议内容存储在远程数据库中,则可以应用原型模式来避免每次HR员工需要创建协议时都会发起网络请求和读取数据库。
为了实现例子的需求,我们创建一个抽象类 PrototypeCapableDocument
实现 Cloneable
, 此类是个基类代表协议并允许克隆,接下来我们用类 TAndC
和 NDAgreement
来对条款和条件以及保密协议进行建模,由于双方签署了保密协议,因此用类AuthorizedSignatory
代表内容制作公司的签名授权,并让他成为类NDAgreement
的成员变量。 最后创建 DocumentPrototypeManager
类,该类创建和存储原型对象,并在客户端请求协议时返回副本,客户将收到原型对象的副本,人力资源经理能够使用供应商的名称更新副本创建协议。
总结一下上面例子各类所担任的角色:
Cloneable
代表它的对象运行克隆PrototypeCapableDocument.java
public abstract class PrototypeCapableDocument implements Cloneable {
private String vendorName;
private String content;
public String getVendorName() {
return vendorName;
}
public void setVendorName(String vendorName) {
this.vendorName = vendorName;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public abstract PrototypeCapableDocument cloneDocument() throws CloneNotSupportedException;
}
TAndC.java
public class TAndC extends PrototypeCapableDocument {
@Override
public PrototypeCapableDocument cloneDocument() {
/*Clone with shallow copy*/
TAndC tAndC = null;
try {
tAndC = (TAndC) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return tAndC;
}
@Override
public String toString() {
return "[TAndC: Vendor Name - " + getVendorName() + ", Content - " + getContent() + "]";
}
}
AuthorizedSignatory.java
public class AuthorizedSignatory implements Cloneable {
private String name;
private String designation;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "[AuthorizedSignatory: Name - " + getName() + ", Designation - " + getDesignation() + "]";
}
}
NDAgreement.java
public class NDAgreement extends PrototypeCapableDocument {
private AuthorizedSignatory authorizedSignatory;
public AuthorizedSignatory getAuthorizedSignatory() {
return authorizedSignatory;
}
public void setAuthorizedSignatory(AuthorizedSignatory authorizedSignatory) {
this.authorizedSignatory = authorizedSignatory;
}
@Override
public PrototypeCapableDocument cloneDocument() throws CloneNotSupportedException {
/*Clone with deep copy*/
NDAgreement nda;
nda = (NDAgreement) super.clone();
AuthorizedSignatory clonedAuthorizedSignatory = (AuthorizedSignatory) nda.getAuthorizedSignatory().clone();
nda.setAuthorizedSignatory(clonedAuthorizedSignatory);
return nda;
}
@Override
public String toString() {
return "[NDAgreement: Vendor Name - " + getVendorName() + ", Content - " + getContent() + ", Authorized Signatory - " + getAuthorizedSignatory() + "]";
}
}
DocumentPrototypeManager.java
public class DocumentPrototypeManager {
private static java.util.Map<String, PrototypeCapableDocument> prototypes = new java.util.HashMap<String, PrototypeCapableDocument>();
static {
TAndC tAndC = new TAndC();
tAndC.setVendorName("Vendor Name Placeholder");
/*Retrieve Terms and Conditions from database/network call/disk I/O here*/
tAndC.setContent("Please read and accept the terms and conditions...");
prototypes.put("tandc", tAndC);
AuthorizedSignatory authorizedSignatory = new AuthorizedSignatory();
authorizedSignatory.setName("Andrew Clark");
authorizedSignatory.setDesignation("Operation Head");
NDAgreement nda = new NDAgreement();
nda.setVendorName("Vendor Name Placeholder");
/*Retrieve Non Disclosure Agreement from database/network call/disk I/O here*/
nda.setContent("Please read and accept the NDA...");
nda.setAuthorizedSignatory(authorizedSignatory);
prototypes.put("nda", nda);
}
public static PrototypeCapableDocument getClonedDocument(final String type) {
PrototypeCapableDocument clonedDoc = null;
try {
PrototypeCapableDocument doc = prototypes.get(type);
clonedDoc = doc.cloneDocument();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clonedDoc;
}
}
DocumentPrototypeManagerTest.java
public class DocumentPrototypeManagerTest {
@Test
public void testGetClonedDocument() throws Exception {
PrototypeCapableDocument clonedTAndC = DocumentPrototypeManager.getClonedDocument("tandc");
clonedTAndC.setVendorName("Mary Parker");
System.out.println(clonedTAndC);
PrototypeCapableDocument clonedNDA = DocumentPrototypeManager.getClonedDocument("nda");
clonedNDA.setVendorName("Patrick Smith");
System.out.println(clonedNDA);
}
}
测试输出结果
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running guru.springframework.gof.prototype.DocumentPrototypeManagerTest
[TAndC: Vendor Name - Mary Parker, Content - Please read and accept the terms and conditions...]
[NDAgreement: Vendor Name - Patrick Smith, Content - Please read and accept the NDA..., Authorized Signatory - [AuthorizedSignatory: Name - Andrew Clark, Designation - Operation Head]]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0 sec - in guru.springframework.gof.prototype.DocumentPrototypeManagerTest
深拷贝&浅拷贝详解