本篇首发于牧码人博客转载请加上此标示。
序言:
继续构建型的模式的学习,今天我们来讲讲原型模式(Prototype)的学习。原型模式的本质是通过复制一个样本来创建一个新对象。简单直接一点就是克隆。
但是克隆一个对象时,这时我们需要思考一个问题。我们克隆的对象是共享里面的属性值。还是继承里面的属性值。作为一个开发者,这一点是需要我们思考与注意的。所以这边便引出一个概念浅复制,与深复制。
什么是浅复制,深复制?
熟悉java的朋友都知道,java自带一个克隆方法(clone)。这是一个native的方法,作为一名系统开发者。可以找一些资料去了解一下这个方法。我这边只能告诉你结论:这个方法不做任何改变的话默认是浅复制的。
- 浅复制
被复制对象的基本数据类型的值跟原来相同,引用对象类型的还是指向原对象的。
- 深复制
被复制对象的基本数据类型的值跟原来相同,引用对象类型的是指向新对象的。
浅复制实践
数据类person
/**
* @Auther: Jan 橙寂
* @Date: 2019-7-27 11:02
* @Description: 原型模式的工具类 需继承 Cloneable 接口
* @Version: 1.0
*/
public class Person implements Cloneable,Serializable {
private int age;
private String name;
private Address address;
public Person(int age, String name, Address address) {
this.age = age;
this.name = name;
this.address = address;
}
/**
* @return 这种方式是浅复制
* @throws CloneNotSupportedException
*/
public Person copy() throws CloneNotSupportedException {
return (Person) super.clone();
}
}
数据类address
public class Address implements Cloneable,Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address(String name)
{
this.name=name;
}
public Address copy() throws CloneNotSupportedException {
return (Address) super.clone();
}
}
测试
/**
* @Auther: Jan 橙寂
* @Date: 2019-7-27 11:27
* @Description:
* @Version: 1.0
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("北京");
Person p = new Person(19, "李四", address);
//浅复制测试
System.out.println("浅复制测试");
Person p1 = p.copy();
System.out.println(p == p1);
p1.getAddress().setName("深圳");
System.out.println(p);
System.out.println(p1);
}
浅复制测试
false
Person{age=19, name='李四', address=Address{name='深圳'}}
Person{age=19, name='李四', address=Address{name='深圳'}}
结论:java默认的clone方法是浅复制,复制后的对象address跟原来的对象指向是一样的。这时大家心里可能会有疑问,如果要使用java默认的clone方法实现深复制。实现深复制的话这边需要改一下原来方法的逻辑,代码如下
使用原生的clone方法实现深复制
/**
* @return 使用原生clone方式实现的深复制
* @throws CloneNotSupportedException
*/
public Person deepCopy() throws CloneNotSupportedException {
Person p = (Person) super.clone();
p.setAddress(p.getAddress().copy());
return p;
}
/**
* @Auther: Jan 橙寂
* @Date: 2019-7-27 11:27
* @Description:
* @Version: 1.0
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("深圳");
Person p = new Person(19, "李四", address);
//深复制实现clone的方式
System.out.println("深复制测试");
Person p2 = p.deepCopy();
System.out.println(p == p2);
p2.getAddress().setName("北京");
System.out.println(p);
System.out.println(p2);
}
根据我们测试用例在来跑一次
深复制测试 false Person{age=19, name='李四', address=Address{name='深圳'}} Person{age=19, name='李四', address=Address{name='北京'}}
深复制总结:
① 如果有一个非原生成员,如自定义对象的成员,那么就需要: 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为public可见。 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。
与对象成员不同,继承关系中的clone不需要被复制类的clone()做多余的工作。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。
使用序列化的方式实现深复制
这个方式可以理解为,把对象转化成二进制流的形式,然后再把流序列化成对象。 具体实现方法
/**
* @return 使用读二进制形式流的方式实现深克隆
* @throws CloneNotSupportedException
*/
public Person deepCopy1() {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
/* 读出二进制流产生的新对象 */
return null;
}
测试
//深复制实现流的方式
System.out.println("深复制测试二进制流");
Person p3 = p.deepCopy1();
System.out.println(p == p3);
p3.getAddress().setName("上海");
System.out.println(p);
System.out.println(p3);
深复制测试二进制流 false Person{age=19, name='李四', address=Address{name='深圳'}} Person{age=19, name='李四', address=Address{name='上海'}}
序列化总结: 这种方式被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。这种是最常用,而且比较简单的方法。
总结
所谓原型模式,就是通过复制对象来创建对象。与通过使用构造函数来比,就是通过复制的对象带有原来对象的一些状态。尤其是在一些场景对象只存在细微差别。这时就可以使用原型模式去创建对象。
源码下载github
注意:本文归作者所有,未经作者允许,不得转载