-
原型模式
原型模式是对象的创建模式,通过给出一个原型对象来指出所有创建对象的类型,然后用复制这个原型对象的方法创建出更多类型的对象。换句话说,就是将一个原型对象传递给到要做创建动作的对象,那个要进行创建动作的对象通过请求原型对象拷贝自己来实现创建过程。这里需要说明的是,同过克隆或者复制所创建的对象都是全新的对象,他们在内存中有各自新的地址。并且每个克隆或者复制对象是相互独立的。 -
原型模式的表现形式
简单形式的原型模式和包含原型管理器的原型模式。这里需要说明什么是原型管理器,所谓的圆形管理器是将多个原型对象存储在一个集合中供用户使用,就相当于一个复制工厂,里面有一个集合存储了所有的原型对象,如果需要赋值,直接通过这个工厂来找到对象的原型对象复制即可。 -
简单形式的原型模式
3.1. 简单形式的原型模式的示意图
3.2. 简单原型模式中的角色
抽象原型角色:声明克隆和复制方法的接口(抽象类),所有具体类型都要实现或者继承这个角色。
具体原型角色:继承或者实现抽象原型角色,是属于被复制的对象,一般需要重新抽象原型角色中的克隆方法,并且在克隆方法中返回自己的一个克隆对象。
用户角色:发起一个创建对象的请求,从而使原型对象克隆自身创建一个新对象返回过来。一般只需要实例化或者通过原型管理器的方式创建一个原型对象,然后在调用改对象的克隆方法既可以得到多个同样的对象。
3.3. 浅克隆与深度克隆
在做示例代码前,我们需要了解一下JAVA中的克隆方式。
一般来说克隆需要满足一下描述:
一. 对任何的对象A,都有A.clone()!=A,换一句话说就是克隆对象和原对象不是同一个对象。
二. 原对象和克隆对象的类型一定是一致的。
三. 如果对象A的equals()方法定义恰当,那么A.clone().equals(A)应该成立。
在以上三个条件中,前两个条件是一定满足的,后面一个则是可选的。
浅克隆:
如果原型角色中的属性是基本的数据类型(byte,short,int,long,char,double,float,boolean)就直接进行复制,如果是引用的对象或者是集合,则其他对象的引用仍然指向原地址。
深度克隆:
除了钱克隆需要克隆的值以外,还要克隆集合和引用类型的对象。使得引用的对象的变量指向被复制的新的对象。
3.4. 浅克隆示例代码
public class Customer implements Cloneable {
private String name,telephone;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
@Override
public Object clone() {
Customer customer = null;
try {
customer = (Customer)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println("当前对象无法克隆");
}
return customer;
}
}
public class Main {
public static void main(String[] args) {
Customer customer = new Customer();
Customer customer2 = (Customer) customer.clone();
System.out.println("customer:"+customer+"||customer2:"+customer2);
System.out.println("克隆的对象向是否相同:"+customer.equals(customer2));
}
}
输出内容:
此时两个原型对象是不相等的,满足需求。
如果再此时我们在客户类中再引入一个地址对象,看看使用这个浅度克隆的输出结果。
public class Address {
private String email,addr,locat;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public String getLocat() {
return locat;
}
public void setLocat(String locat) {
this.locat = locat;
}
}
在customer里面加上这个属性,以及get,set方法。
测试主函数:
public class Main {
public static void main(String[] args) {
Customer customer = new Customer();
Address address = new Address();
customer.setAddress(address);
Customer customer2 = (Customer) customer.clone();
System.out.println("customer:"+customer+"||customer2:"+customer2);
System.out.println("克隆的对象向是否相同:"+customer.equals(customer2));
System.out.println("克隆的地址向是否相同:"+(customer.getAddress()).equals((customer2.getAddress())));
}
}
控制台输出结果:
可以看到,此时对象不同,但是地址却是相同的,说明他们在内存中是同一个地址。这里就对浅克隆的局限性做了验证。因此此时我们需要用到深度克隆。
3.5. 深度克隆示例代码
在JAVA中可以利用序列化来实现深度克隆。序列化是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原有对象仍然存在于内存中,通过序列化实现拷贝不仅仅可以复制对象本身,而且可以渎职其引用对象的成员对象,因此通过序列化将对象写到一个流中,再从流中读出来,可以实现深度克隆。需要注意,JAVA对象如果需要实现序列化就必须实Serializable接口,否则无法序列化。
public class Customer implements Serializable {
private String name,telephone;
private Address address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public Address getAddress() {
return this.address;
}
public void setAddress(Address address) {
this.address = address;
}
//关键代码
public Object deepClone() throws IOException, ClassNotFoundException {
//将对象写到流里
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
//从流里读回来
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}
测试主函数:
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Customer customer = new Customer();
Address address = new Address();
customer.setAddress(address);
Customer customer2 = (Customer) customer.deepClone();
System.out.println("customer:"+customer+"||customer2:"+customer2);
System.out.println("克隆的对象向是否相同:"+customer.equals(customer2));
System.out.println("克隆的地址向是否相同:"+(customer.getAddress()).equals((customer2.getAddress())));
}
}
控制台输出:
注意:
JAVA中Cloneable和Serializable的代码非常简单,都是空接口,这些类似的空接口又称为标识接口,这些接口里面没有任何方法的定义,其作用就是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
4.原型管理器
4.1. 原型管理器模式示意图
相比较上面的原型模式来说,他多了一个原型管理器角色。该角色的作用是:创建具体原型类的对象,并记录每一个被创建的对象。
4.2. 原型管理器模式示例代码
场景:合同模板
原型:
public interface Contract extends Cloneable{
public Contract clone();
void display();
}
public class LegalContract implements Contract {
@Override
public Contract clone() {
Contract legalContract = null;
try {
legalContract = (Contract)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println("不支持复制!");
}
return legalContract;
}
@Override
public void display() {
System.out.println("这是一份法务合同");
}
}
public class PersonnelContract implements Contract {
@Override
public Contract clone() {
Contract personnelContract = null;
try {
personnelContract = (Contract)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
System.out.println("不支持复制!");
}
return personnelContract;
}
@Override
public void display() {
System.out.println("这是人事合同");
}
}
原型管理器 :
public class ContractManager {
private Hashtable contractTable = new Hashtable();
private static ContractManager contractManager = new ContractManager();
private ContractManager() {
contractTable.put("person",new PersonnelContract());
contractTable.put("legal",new LegalContract());
}
public void addContract(String name,Contract contract){
contractTable.put(name,contract);
}
public Contract getContract(String name){
//这里需要注意
return ((Contract)contractTable.get(name)).clone();
}
public static ContractManager getContractManager() {
return contractManager;
}
}
测试主函数:
public class Main {
public static void main(String[] args) {
ContractManager contractManager = ContractManager.getContractManager();
Contract contract1,contract2,contract3,contract4;
contract1 = contractManager.getContract("person");
contract1.display();
contract2 = contractManager.getContract("person");
contract2.display();
System.out.println(contract1.equals(contract2));
contract3 = contractManager.getContract("legal");
contract3.display();
contract4 = contractManager.getContract("legal");
contract4.display();
System.out.println(contract3.equals(contract4));
}
}
控制台输出结果:
在ContractManager中定义了一个集合对象,使用键值对的方式来存储原型对象,用户可以用过键来获取对应对象的克隆对象,通过getContract()
方法来返回克隆对象。在这里将ContractManager一般设计为单例,确保系统中有且仅有一个原型管理器对象,有利于节省资源,更好的对原型管理器进行控制。
5. 原型管理模式的优缺点
优点:
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
缺点:
配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
必须实现 Cloneable 接口。