设计模式之原型模式
定义
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。可以简单类比于孙悟空用毫毛变化出很多和自己一模一样的小猴兵。
结构
- Prototype,原型接口,定义了克隆自身的方法。
- ConcretePrototype,具体原型类,实现了原型接口。
- Client,使用原型的客户端。
简单实现
原型接口
public interface Prototype {
/**
* 克隆自身
*/
Prototype clone();
}
具体原型类
public class ConcretePrototype implements Prototype {
private String name;
private int age;
public ConcretePrototype(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public Prototype clone() {
return new ConcretePrototype(this.name, this.age);
}
@Override
public String toString() {
return this.name + "," + age;
}
}
客户端
public class Client {
public static void main(String[] args) {
//创建原型对象
ConcretePrototype prototype = new ConcretePrototype("lisi", 24);
System.out.println(prototype);//lisi,24
//根据原型对象克隆出新的对象
ConcretePrototype clonedPrototype = (ConcretePrototype) prototype.clone();
System.out.println(clonedPrototype);//lisi,24
System.out.println(prototype == clonedPrototype);//false
System.out.println(prototype.getName() == clonedPrototype.getName());//true
}
}
java本身提供了 Cloneable 接口,我们只需要重写 Object 中的 clone()方法就可以了
public class ConcretePrototype implements Cloneable {
private String name;
private int age;
public ConcretePrototype(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
@Override
public String toString() {
return this.name + "," + age;
}
}
客户端代码和上面一样,Cloneable 接口下没有定义方法,只是作为一个声明,如果重写了clone()方法但没有实现此接口,就会抛出 CloneNotSupportedException 异常。
原型模式在JDK的实现
JDK中ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/**
* Returns a shallow copy of this {@code ArrayList} instance. (The
* elements themselves are not copied.)
*
* @return a clone of this {@code ArrayList} instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
}
客户端使用
import java.util.ArrayList;
public class TestArrayListClone2 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("lisi");
names.add("lisi2");
System.out.println(names);
ArrayList<String> clonedNames = (ArrayList<String>) names.clone();
System.out.println(clonedNames);
}
}
其他类似的LinkedList,HashMap也都实现了 Cloneable 接口。
浅克隆和深克隆
- 浅克隆:只负责克隆基本数据类型的数据
- 深克隆:基本数据类型和引用类型的数据都会被克隆,如果属性中还包含引用类型的属性,会一直递归的克隆下去。
ArrayList等类中的克隆实现都是浅克隆,一般通过序列化方式来实现深克隆。
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
public class TestArrayListClone {
public static void main(String[] args) {
testShallowCopy();
testDeepCopy();
}
private static void testShallowCopy() {
ArrayList<User> users = new ArrayList<>();
users.add(new User("lisi"));
ArrayList<User> clonedUsers = (ArrayList<User>) users.clone();
System.out.println("====浅克隆====");
System.out.println(clonedUsers.size() == users.size());
System.out.println(clonedUsers == users);
System.out.println(clonedUsers.get(0) == users.get(0));
}
private static void testDeepCopy() {
ArrayList<User> users = new ArrayList<>();
users.add(new User("lisi"));
ArrayList<User> clonedUsers = (ArrayList<User>) deepCopy(users);
System.out.println("====深克隆====");
System.out.println(clonedUsers.size() == users.size());
System.out.println(clonedUsers == users);
System.out.println(clonedUsers.get(0) == users.get(0));
}
private static Object deepCopy(Object obj) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
byte[] data = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream bis = new ObjectInputStream(bais);
return bis.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private static class User implements Serializable {
public User(String name) {
this.name = name;
}
String name;
}
}
输出结果为
====浅克隆====
true
false
true
====深克隆====
true
false
false
可以看到,浅克隆的结果中User对象是同一个,深克隆结果中User对象是两个不同的对象。
总结
优点
- 当对象实例比较复杂时,使用原型模式可以简化创建新对象的过程,并且 java 自带的克隆实现是基于内存中二进制流的复制,相比于直接 new 一个对象,性能上更加优良。
缺点
- clone方法位于类的内部,当对已有类进行改造的时候,可能也需要修改克隆方法,违背了开闭原则。
- 在不使用序列化的情况下,为了支持深克隆,当对象之间存在多重嵌套引用关系时,每一层对象都必须支持深克隆,实现起来可能比较麻烦。
本质
原型模式的本质是克隆生成对象,克隆是手段,生成对象是目的。
使用场景
- 当创建新对象成本较大时(例如初始化需要较长的时间,占用太多的CPU资源或者网络资源),这种情况下可以考虑使用原型模式来创建新对象。