模式动机
在软件开发中,有时某些对象创建的过程比较复杂,而且需要频繁创建,那么我们可不可以直接Ctrl C再Ctrl V呢?答案是可以的,原型模式就是解决这类问题,它通过复制一个对象本身,从而克隆出多个与原型对象一模一样的对象出来。
模式定义
用原型实例指定创建对象的种类,然后通过复制这个原型对象创建出新的对象,无须知道创建的细节,这便是原型模式。
Java中的克隆
java中所有的类都继承与java.lang.Object,这个类中有一个本地方法clone(),可以将一个java对象复制一份,因此在java中可以使用这个方法来实现对象的克隆,java中的原型模式的实现也因此变得很简单。另外,要想使用clone(),必须实现Cloneable接口,表示这个java类是支持复制的,如果一个类没有实现这个接口,但却调用了clone()方法,那将会报出CloneNotSupportedException的异常。
模式结构
原型模式主要由以下两部分组成:
抽象原型类:如Prototype
具体原型类:如ConcretePrototypeA
代码实例
抽象原型类
public interface Prototype {
public Object copy();
}
具体原型类
public class Email implements Prototype,Cloneable{
private String title;
@Override
public Email copy() {
try {
return (Email) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
客户端测试类
public class Client {
public static void main(String[] args) {
Email e1 = new Email();
e1.setTitle("我是邮件一");
Email e2 = e1.copy();
System.out.println(e2.getTitle());
}
}
运行客户端测试类,输出为
我是邮件一
至此,一个最简单的原型模式已经完成。
深克隆和浅克隆
在上面的例子中,我们的原型对象Email非常简单,里面只有一个title属性,假如里面包含各种类型,甚至包含一个对象,此时又该如何克隆呢?此时,有两种不同的克隆方式,浅克隆和深克隆。
浅克隆也叫浅度拷贝或者浅度复制:只复制原对象里的8种基本类型及其封装类和String类型,对于对象类型,不会复制,新对象中仍然是原来的引用。
深克隆也叫深度拷贝或者深度复制:不仅复制对象里的8种基本类型及其封装类和String类型,同时也复制原对象类型,完全产生新对象。下面通过实例来说明:
- 浅克隆
public class Attachment {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Email implements Prototype,Cloneable{
private String title;
private int intA;
private Integer integerA;
private Attachment attachment;//这里是另外的一个对象
@Override
public Email copy() {
try {
return (Email) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getIntA() {
return intA;
}
public void setIntA(int intA) {
this.intA = intA;
}
public Integer getIntegerA() {
return integerA;
}
public void setIntegerA(Integer integerA) {
this.integerA = integerA;
}
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
}
public class Client {
public static void main(String[] args) {
Email e1 = new Email();
e1.setTitle("我是title");
e1.setIntA(1);
e1.setIntegerA(new Integer(1));
Attachment attachment1 = new Attachment();
attachment1.setName("附件1");
e1.setAttachment(attachment1);
Email e2 = e1.copy();
System.out.println(e1.getAttachment() == e2.getAttachment());//true,说明是同一个引用,并没有复制一个新的对象
e1.setTitle("title发生改变");
System.out.println(e1.getTitle());//title发生改变
System.out.println(e2.getTitle());//我是title
//克隆对象e2的title并没有发生改变,说明String类型是复制了的
e1.setIntA(2);
System.out.println(e1.getIntA());//2
System.out.println(e2.getIntA());//1
//克隆对象e2的intA并没有发生改变,说明int类型是复制了的
e1.setIntegerA(new Integer(2));
System.out.println(e1.getIntegerA());//2
System.out.println(e2.getIntegerA());//1
//克隆对象e2的integerA并没有发生改变,说明Integer类型是复制了的
e1.getAttachment().setName("attachment的name发生改变");
System.out.println(e1.getAttachment().getName());//attachment的name发生改变
System.out.println(e2.getAttachment().getName());//attachment的name发生改变
//e1修改了attachment的name,e2的attachment的name也跟着变了,说明attachment并没有被复制,e1的attachment跟e2的attachment仍然是同一个。说明对于对象类型,调用clone()方法,并不会被复制,这就是浅克隆
}
}
- 深克隆
//这里跟上面略有不同,这里需要实现Serializable接口,让其可以序列化,因为克隆的时候需要写到流里面
public class Attachment implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//这里也要实现Serializable接口
public class Email implements Prototype,Serializable{
private static final long serialVersionUID = 1L;
private String title;
private int intA;
private Integer integerA;
private Attachment attachment;
@Override
public Email copy() {
try {
//这里的克隆方法跟浅克隆就完全不一样了,浅克隆是直接调用clone()方法即可,而深度克隆需要将对象写入流,然后再从流中取出来
//先把对象写入流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//然后把对象从流里取出来
byte b[] = bos.toByteArray();
ByteArrayInputStream bis = new ByteArrayInputStream(b);
ObjectInputStream ois = new ObjectInputStream(bis);
return (Email)ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getIntA() {
return intA;
}
public void setIntA(int intA) {
this.intA = intA;
}
public Integer getIntegerA() {
return integerA;
}
public void setIntegerA(Integer integerA) {
this.integerA = integerA;
}
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
}
public class Client {
public static void main(String[] args) {
Email e1 = new Email();
e1.setTitle("我是title");
e1.setIntA(1);
e1.setIntegerA(new Integer(1));
Attachment attachment1 = new Attachment();
attachment1.setName("附件1");
e1.setAttachment(attachment1);
Email e2 = e1.copy();
System.out.println(e1.getAttachment() == e2.getAttachment());//false,说明不是同一个引用!!!而是一个全新的对象!!!
e1.setTitle("title发生改变");
System.out.println(e1.getTitle());//title发生改变
System.out.println(e2.getTitle());//我是title
//克隆对象e2的title并没有发生改变,说明String类型是复制了的
e1.setIntA(2);
System.out.println(e1.getIntA());//2
System.out.println(e2.getIntA());//1
//克隆对象e2的intA并没有发生改变,说明int类型是复制了的
e1.setIntegerA(new Integer(2));
System.out.println(e1.getIntegerA());//2
System.out.println(e2.getIntegerA());//1
//克隆对象e2的integerA并没有发生改变,说明Integer类型是复制了的
e1.getAttachment().setName("attachment的name发生改变");
System.out.println(e1.getAttachment().getName());//attachment的name发生改变
System.out.println(e2.getAttachment().getName());//附件1
//e1修改了attachment的name,e2的attachment的name却没有随之改变,依然保持原来的值"附件1",说明attachment也被复制了,是一个全新的对象,这就是深克隆
}
}
对比上面两种代码可以看出,浅克隆和深克隆主要有以下不同:
- 浅克隆不会复制对象类型的属性,深克隆要复制
- 实现方式不一样,浅克隆是调用clone(),深克隆是使用输入输出流;
- 浅克隆要实现Cloneable接口,而深克隆不用,但深克隆中原型对象以及里面所包含的对象,要实现Serializable接口
模式优点
- 当创建的对象比较复杂时,原型模式可以简化创建对象的过程,直接通过一个实例复制得到;
- 浅克隆是调用clone()方法,这是一个本地(native)方法,直接操作的内存中的二进制流,深度克隆也是通过输入输出流进行复制,性能上比通过new要好,所以当复制对象较大时,通过原型模式可以大大提升性能。
总结
主要讲了几点,一是java中的clone()方法,必须要实现Cloneable接口;二是深克隆和浅克隆,主要区别在于复制对象类型时,浅克隆不会复制,而深克隆会复制;三是模式的优点,简化创建过程,同时提高了性能。