什么是原型模式
听说过克隆技术吧?再通俗点,就是复制。
原型模式就是代码里面的克隆技术,把一个已经实例化的对象当作原型,克隆出一个跟它相同的对象。
可以采用new
方式构造一个新对象,再把原型对象的内部状态拷贝过来,达到复制原型对象的目的,但是这就涉及关心到了原型内部,没有克隆模式来的简单直接。
原型模式的实现思路
从是否具备克隆能力的角度看,有两种实现思路:
- 原型自身具备克隆能力,就像孙悟空,拔根猴毛就能变出一个分身
- 原型自身不具备克隆能力,就像克隆羊多利,是生物学家掌握克隆技术,从母体身上克隆出来的
从Java
代码实现角度看,有三种实现方案:
- 利用
Object
类提供的clone
方法实现,有浅克隆、深克隆两种模式 - 利用对象序列化机制实现,只有深克隆模式
- 还有一种方法就当发散思维了,那就是直接
new
新对象,再将原型模式属性都复制过来(实际不这么干,也不推荐)
clone()
因为Object
类里面提供有clone
方法,而Object
类又是所有类的隐形基类,所以,通过继承的途径,每个对象就天然拥有自己克隆自己的潜力。
但是这个clone()
只能做到浅克隆模式,要做到深克隆还需要自己动手多做点事情。
序列化
序列化就是把内存中的对象信息存储起来,需要的时候,再通过反序列化将储存信息转化成内存中的对象信息。
这句话有两个要点:
- 序列化是转换内存对象信息的过程,不包括对象的内存地址信息
- 反序列化是根据对象信息重建内存对象的过程
因此,这种方法只能实现深克隆模式,不能实现浅克隆模式。
原型模式的实现代码
浅克隆
浅克隆的意思是:克隆出来的对象,所有“基本类型变量值”和“引用类型变量值”都和原型对象一样。
也就是说,如果原型对象中有一个属性指向对象A
,那么克隆对象相同的属性也指向对象A
。
浅克隆只复制原型对象、以及原型对象属性的基本类型值和引用值,不会复制内部属性引用的其它对象。
代码实现分两步走:
- 原型类实现
java.lang.Cloneable
接口,这是一个空的标记接口,并无实际作用,只是遵循Java
的编程规范,表明这类对象可以克隆。 - 原型类重写
Object
类中的clone
方法,并将其访问修饰符变更为public
,重写逻辑里面需要调用Object
的clone
方法,也就是super.clone()
。
代码实现:
public class Teacher {
}
// 原型
public class Student implements Cloneable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
// 测试
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student(new Teacher());// 原型对象
Student clone = student.clone();// 克隆对象
System.out.println(student.getTeacher() == clone.getTeacher());// true
}
深克隆
深克隆的意思是:克隆出来的对象,所有“基本类型变量值”和原型对象相同,“引用类型变量值”和原型对象不同。
也就是说,如果原型对象中有一个属性指向对象A
,那么,对象A
也要被克隆,最后,克隆对象相同的属性不是指向A
对象,而是指向新对象A'
。
深克隆不仅复制原型对象,还会复制原型对象内部属性的引用对象,克隆的特别透彻。
实现深克隆,就在浅克隆基础上多做一点事情:手动把原型所有的引用属性再克隆一次。
前面说了,深克隆有两种方法:clone ()
、序列化。
第一种clone()
,代码实现:
public class Teacher implements Cloneable {
@Override
protected Teacher clone() throws CloneNotSupportedException {
return (Teacher) super.clone();
}
}
// 原型
public class Student implements Cloneable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
@Override
public Student clone() throws CloneNotSupportedException {
Student clone = (Student) super.clone();
clone.teacher = teacher.clone(); // 手动赋值,深克隆需要自己编码多做事情
return clone;
}
}
// 测试
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student(new Teacher());// 原型对象
Student clone = student.clone();// 克隆对象
System.out.println(student.getTeacher() == clone.getTeacher());// false
}
第二种序列化,需要注意一点,通过这种方式进行的深克隆,相关类都需要实现java.io.Serializable
接口,这是Java
序列化机制要求的。代码实现:
public class Teacher implements Serializable {
}
// 原型
public class Student implements Serializable {
private Teacher teacher;
public Student(Teacher teacher) {
this.teacher = teacher;
}
public Teacher getTeacher() {
return teacher;
}
// 因为没有利用Object的clone方法,所以,最好也别重写clone方法,新定义一个方法比较好
public Student 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 (Student) ois.readObject();
}
}
// 测试
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(new Teacher());
Student clone = student.deepClone();
System.out.println(student.getTeacher() == clone.getTeacher());
}
原型模式的优缺点
优点
一旦构建好原型模式的克隆逻辑,就可以很方便的得到一个和原型一摸一样的对象,不需要关心原型的内部信息。
缺点
需要理解浅克隆和深克隆的含义,不能在这上面犯错。
克隆逻辑往往具有全局影响力,设计的时候需要全盘考量,对原来的克隆逻辑进行改动更是要慎之又慎!
当对象引用属性包含着循环结构的时候,特别容易出问题!
采用序列化机制的时候,还要考虑对象引用属性是否都能继承Serializable
接口,如果属性对象是第三方jar
包里的,很可能就会出现不支持序列化的问题,要慎重!