原型模式
定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。用这种方式创建对象非常高效,根本无须知道对象创建的细节。
原型模式的克隆分为浅克隆和深克隆。
浅克隆
创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆
创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
优点
- Java自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
结构
原型模式包含以下主要角色。
- 抽象原型类:规定了具体原型对象必须实现的接口。
- 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
- 访问类:使用具体原型类中的 clone() 方法来复制新的对象。
要求
浅克隆
- 实现接口Cloneable
- 重写方法clone()
- 设置方法clone()为public。
深克隆
所有的对象都需要实现java.io.Serializable接口,并且可以序列化。
应用场景
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候。
- 创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
- 创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
- 系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
示例
现在有一只熊猫(有自己的名字和巢穴),需要克隆出另外的一只熊猫。两只熊猫的属性完全一样,但是却不是同一个对象。
浅克隆
浅克隆的时候,只会对需要克隆的对象进行克隆,但是不会对被设置为属性的对象进行克隆。
巢穴
package com.designpattern.prototype.shallowclone.objects;
public class Lair {
private String name;
public Lair(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Lair{" + "name='" + name + '\'' + '}';
}
}
熊猫
package com.designpattern.prototype.shallowclone.objects;
public class Panda implements Cloneable{
private String name;
private Lair lair;
public Panda(String name, Lair lair) {
this.name = name;
this.lair = lair;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Lair getLair() {
return lair;
}
public void setLair(Lair lair) {
this.lair = lair;
}
@Override
public String toString() {
return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + '}';
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
克隆
package com.designpattern.prototype.shallowclone;
import com.designpattern.prototype.shallowclone.objects.Lair;
import com.designpattern.prototype.shallowclone.objects.Panda;
public class Application {
public static void main(String[] args) throws CloneNotSupportedException {
Panda panda1 = new Panda("A", new Lair("A"));
Panda panda2 = panda1.clone();
System.out.println("panda1 = " + panda1);
System.out.println("panda2 = " + panda2);
System.out.println("panda1 == panda2 is " + (panda1 == panda2));
System.out.println("panda1.getLair() == panda2.getLair() is " + (panda1.getLair() == panda2.getLair()));
}
}
输出
panda1 = Panda{name='A', lair=Lair{name='A'}}
panda2 = Panda{name='A', lair=Lair{name='A'}}
panda1 == panda2 is false
panda1.getLair() == panda2.getLair() is true
深克隆
深克隆的时候,需要每一个属性都满足克隆要求(实现Serializable接口)。
巢穴·改
package com.designpattern.prototype.deepclone.objects;
import java.io.Serializable;
public class Lair implements Serializable {
private String name;
public Lair(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Lair{" + "name='" + name + '\'' + '}';
}
}
熊猫·改
package com.designpattern.prototype.deepclone.objects;
import java.io.*;
public class Panda implements Serializable {
private String name;
private Lair lair;
public Panda(String name, Lair lair) {
this.name = name;
this.lair = lair;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Lair getLair() {
return lair;
}
public void setLair(Lair lair) {
this.lair = lair;
}
@Override
public String toString() {
return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + '}';
}
//需要关闭流
public Panda deepClone() throws IOException, ClassNotFoundException {
//将对象写到流里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Panda) ois.readObject();
}
}
克隆
package com.designpattern.prototype.deepclone;
import com.designpattern.prototype.deepclone.objects.Lair;
import com.designpattern.prototype.deepclone.objects.Panda;
import java.io.IOException;
public class Application {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Panda panda1 = new Panda("A", new Lair("A"));
Panda panda2 = panda1.deepClone();
System.out.println("panda1 = " + panda1);
System.out.println("panda2 = " + panda2);
System.out.println("panda1 == panda2 is " + (panda1 == panda2));
System.out.println("panda1.getLair() == panda2.getLair() is " + (panda1.getLair() == panda2.getLair()));
}
}
输出
panda1 = Panda{name='A', lair=Lair{name='A'}}
panda2 = Panda{name='A', lair=Lair{name='A'}}
panda1 == panda2 is false
panda1.getLair() == panda2.getLair() is false
总结
使用浅克隆的时候,克隆对象和原对象使用相同的引用对象,但是只需要对被克隆对象类实现Cloneable接口即可。深克隆的时候,需要对所有要求被克隆的属性实现Serializable接口,层级不容易控制。
如果一个属性对象不想被复制,可以使用transient关键字来确保该对象(该对象无须实现Serializable接口或者Cloneable接口)不会被复制,新对象的该值为null。
静态变量不管是否被transient修饰,均不能被序列化,反序列化后类中static型变量的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的。
此外,如果一个属性对象中有部分信息(不管该信息是否被transient修饰)需要被序列化,可以通过实现Externalizable接口,并重写它的方法来制定需要实例化的信息(或者添加指定的writeObject和readObject方法)。
package com.designpattern.prototype.deepclone.objects;
import java.io.*;
public class Panda implements Serializable {
private static final long serialVersionUID = -1L;
private String name;
private Lair lair;
private transient Toy toy;
private transient Bed bed;
public Panda(String name, Lair lair, Toy toy, Bed bed) {
this.name = name;
this.lair = lair;
this.toy = toy;
this.bed = bed;
}
@Override
public String toString() {
return "Panda{" + "name='" + name + '\'' + ", lair=" + lair + ", toy=" + toy + ", bed=" + bed + '}';
}
public Panda deepClone() throws IOException, ClassNotFoundException {
//将对象写到流里
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
//从流里读回来
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Panda) ois.readObject();
}
//写入内存
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeObject(this.name);
out.writeObject(this.lair);
out.writeObject(this.bed);
}
//从内存读取
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
lair = (Lair) in.readObject();
bed = (Bed) in.readObject();
}
}
此外,还可以与其他模式混合使用,尤其是备忘录模式,作为备份或者快照使用。也可以使用一个额外的类来管理所有的需要复制的对象(提供两个基础方法:保存对象,复制对象并返回),每次只需要从管理者获得复制对象,而不需要自己去识别系统中是否已经存在了该对象。