原型模式基本介绍
所谓的原型模式是一个创造型的模式,既然叫做原型模式,即表明了该模式实现中有一个样板实例,客户端从这个样板对象中,复制出一个内部属性一致的对象进行使用,即clone()。通常当对象创建过于复杂,或者构造实例的时候较为耗时的情况下,我们使用原型模式,可以大量节约性能,提高程序效率。
原型模式UML图
Client:客户端用户
Prototype:抽象类或者接口,声明具备clone能力
ConcretePrototype:具体的原型类
原型模式关键点
1)通过实现Clone able接口后在调用clone创建实例时并不是一定比new的操作速度快,只有在使用new构造对象较为耗时或者成本较高时,才会显出优势。
2)clone()方法并不是Cloneable接口中的的,而是Object中的方法,使用Cloneable是用来标明这个对象是可以被拷贝的。
代码示例
我们来看一段使用原型模式的代码
static class School implements Cloneable {
String schoolName;
ArrayList<String> students = new ArrayList<String>();
@Override
protected School clone() {
try {
School s = (School) super.clone();
s.schoolName = this.schoolName;
s.students = this.students;
return s;
} catch (Exception e) {
}
return null;
}
@Override
public String toString() {
String studentsStr = "";
for (String s :
students) {
studentsStr += s + " ";
}
return "Student:" + "SchoolName-" + this.schoolName + ",Student-" + studentsStr;
}
}
先建立了一个School类,并且继承了Cloneable接口,重写了clone方法。接下来看main();
public static void main(String[] args) {
School s1 = new School();
s1.schoolName = "北京一中";
s1.students.add("小明");
School s2 = s1.clone();
System.out.println(s1);
System.out.println(s2);
System.out.println("---------------------修改s2----------------------");
s2.schoolName = "北京二中";
s2.students.remove("小明");
s2.students.add("小芳");
System.out.println(s1);
System.out.println(s2);
}
main函数显示创建了一个School对象s1,接着使用s1的clone方法克隆出了s2,我们先看输出结果。
在修改S2的之前,s1和s2的输出内容是一样的,说明s2成功通过s1的clone方法克隆了一个School的实例。
接着我们修改了s2中的schoolName以及students方法,我们发现s1中的school Name没有被修改,但是Students信息却被一起修改了。
因为在School的clone方法中,我们只是简单的引用了原本的值,当原型中的字段不是基本的值类型而是对象类型的时候,这样的引用只是指向了原型的对象的地址中。由于String schoolName是基础值类型,所以是将值直接赋给了s2中的schoolName,而对于students这样的对象,我们在clone方法中同样也需要调用他的clone方法,这就是原型模式中的 深拷贝与浅拷贝 问题,
浅拷贝
既简单的拷贝了原型对象的第一层结构。
深拷贝
对原型对象进行递归式的拷贝,将深层次的值也进行拷贝。
根据以上定义,表明我们需要修改School中的clone方法,students字段不能简单的使用等号来引用,而是使用他的clone方法来拷贝一份。
clone方法修改如下
@Override
protected School clone() {
try {
School s = (School) super.clone();
s.schoolName = this.schoolName;
s.students = (ArrayList<String>)this.students.clone();
return s;
} catch (Exception e) {
}
return null;
}
运行结果如下
可以看到s1中的Students并没有被修改。只是修改了拷贝出来的对象。
小结
使用原型模式可以直接做出对原型对象的拷贝,这样在处理复杂度较高的对象时有着较为明显的优势,并且可以通过对外开放拷贝出的副本对象,让外界只能修改副本,而不破坏原型自身,减少外界的影响给自身带来的Bug,即所谓的保护性拷贝。
不过需要注意的是,当使用clone来创建对象的时候,是不会执行构造方法的,所以当使用原型模式时,需要特别留意一下构造方法中是否有必须要执行的代码。