一.什么是原型模式
原型模式是指原型对象指定创建对象的种类,并通过拷贝这些原型的方式创建新的对象。
调用者不需要知道创建对象的细节,不调用构造函数就能创建对象。
属于创建型模式。
目的是为了减少创建对象的复杂度,提升系统性能,一般是基于二进制流的拷贝。
二.原型模式的通用写法
我们先来看一下原型模式的通用写法
先创建一个顶层接口
public interface IProtoType<T> {
// 第一种方法
T clone();
// 第二种方法
T clone(T object);
}
再创建一个类来实现这个接口
public class CommonProtoType implements IProtoType{
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
// 第一种方式,需要每个字段进行赋值,适合字段比较少的情况
@Override
public CommonProtoType clone() {
CommonProtoType commonProtoType = new CommonProtoType();
commonProtoType.setId(this.getId());
commonProtoType.setAge(this.getAge());
commonProtoType.setName(this.getName());
return commonProtoType;
}
// 第二种方式,利用反射为属性赋值,适合字段较多的情况
@Override
public Object clone(Object object) {
try {
Class clazz = object.getClass();
Object returnValue = clazz.newInstance();
Field[] fileds = clazz.getDeclaredFields();
for (Field filed : fileds) {
filed.setAccessible(true);
filed.set(returnValue, filed.get(object));
}
return returnValue;
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
三.原型模式的使用场景
1.new一个对象并且给属性赋值的过程非常繁琐
2.构造方法比较复杂
3.循环体内大量创建具有相同属性的对象
4.类初始化消耗资源较多
注意:克隆和new的区别:克隆不会走构造方法
四.浅克隆和深克隆
1.浅克隆
在拷贝引用数据类型的时候会拷贝引用的内存地址,而不拷贝具体的值(String除外,因为它的value是final的)。
先来看一个案例
创建一个浅克隆的类实现Cloneable接口,实现clone方法,直接调用父类的clone方法。Object类实现了Cloneable接口,是一个本地方法,默认是浅克隆
public class ShallowProtoType implements Cloneable {
private Integer id;
private String name;
private Integer age;
private List<String> hobbies;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
@Override
public ShallowProtoType clone() {
try {
return (ShallowProtoType) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "ShallowProtoType{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
}
创建测试类
public class Test {
public static void main(String[] args) {
ShallowProtoType shallowProtoType = new ShallowProtoType();
shallowProtoType.setId(1);
List<String> hobbies = new LinkedList<String>() {
{
this.add("打篮球");
this.add("打游戏");
}
};
shallowProtoType.setHobbies(hobbies);
shallowProtoType.setAge(18);
shallowProtoType.setName("周杰伦");
ShallowProtoType cloneType = shallowProtoType.clone();
System.out.println("改变克隆对象的爱好前:");
System.out.println("原型对象" + shallowProtoType);
System.out.println("克隆对象" + cloneType);
System.out.println("改变克隆对象的爱好后:");
cloneType.getHobbies().add("唱歌");
System.out.println("原型对象" + shallowProtoType);
System.out.println("克隆对象" + cloneType);
}
}
输出结果
可以看出,克隆对象的引用类型的属性改变之后,原型对象也跟着改变了。
2.深克隆
为了避免浅克隆带来的问题,由此引入深克隆的方式。
还记得我们之前讲过的序列化和反序列会破坏单例吗?这里就要利用这一点,在序列化和反序列的时候,不实现自己的readResolve方法,那么每次就会返回一个不同的实例。
创建一个深克隆的类,实现Serializable接口。
public class DeepProtoType implements Serializable {
private Integer id;
private String name;
private Integer age;
private List<String> hobbies;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public DeepProtoType deepClone() {
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (DeepProtoType) objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public String toString() {
return "DeepProtoType{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", hobbies=" + hobbies +
'}';
}
}
创建测试类
public class Test {
public static void main(String[] args) {
DeepProtoType deepProtoType = new DeepProtoType();
deepProtoType.setId(1);
List<String> hobbies = new LinkedList<String>() {
{
this.add("打篮球");
this.add("打游戏");
}
};
deepProtoType.setHobbies(hobbies);
deepProtoType.setAge(18);
deepProtoType.setName("周杰伦");
DeepProtoType cloneType = deepProtoType.deepClone();
System.out.println("改变克隆对象的爱好前:");
System.out.println("原型对象" + deepProtoType);
System.out.println("克隆对象" + cloneType);
System.out.println("改变克隆对象的爱好后:");
cloneType.getHobbies().add("唱歌");
System.out.println("原型对象" + deepProtoType);
System.out.println("克隆对象" + cloneType);
}
}
输出结果
可以看出,深克隆完美解决了浅克隆带来的问题
除了序列化和反序列的方式外,可以利用json来实现深克隆,阿里和谷歌的json库都可以。
五.原型模式在源码中的应用
ArrayList、HashMap
这两个都是浅克隆,只要是通过实现Cloneable接口来实现克隆的都是浅克隆,深克隆一般要自己去实现。