简介:原型模式是一个创建型模式,用户从这个样本对象中复制出一个内部属性一致的对象,这个过程也就是俗称的“克隆”。
定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
使用场景:
1.类的初始化需要消耗非常多资源,通过原型拷贝可以避免这些重复消耗。
2.通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用。
3.一个对象需要提供给提供给其他对象访问,而且各个调用者可能都需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者实现
接下来 我们就以学生对象为例,让它实现Cloneable接口:
需要注意的是,通过实现Cloneable接口的原型模式在调用clone方法的时候不一定比new速度快,只有当new这个操作会消耗非常多的资源,通过clone方法才能获得效率的提升,因此,在使用时需要考虑构建对象的成本以及做一些效率上的测试。
Student.java:
public class Student implements Cloneable {
private String name;
private ArrayList<String> hobbies = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ArrayList<String> getHobbies() {
return hobbies;
}
public void setHobbies(ArrayList<String> hobbies) {
this.hobbies = hobbies;
}
public void addHobby(String hobby) {
hobbies.add(hobby);
}
public Student() {
System.out.println("构造函数");
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
stu.name = this.name;
stu.hobbies = this.hobbies;
return stu;
}
public void showStudentInfo() {
System.out.println("Start-----------------");
System.out.println("name:" + name);
System.out.println("list:");
for (String hobby : hobbies) {
System.out.println("hobby name:" + hobby);
}
System.out.println("Stop------------------");
}
}
client.java:
public class Client {
public static void main(String[] args) {
Student stu = new Student();
stu.setName("张三");
stu.addHobby("游泳");
stu.addHobby("跳绳");
stu.addHobby("篮球");
stu.showStudentInfo();
try {
Student stu2 = (Student) stu.clone();
stu2.showStudentInfo();
stu2.setName("李四");
stu2.addHobby("游戏");
stu2.showStudentInfo();
stu.showStudentInfo();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
输出结果:
Start-----------------
name:张三
list:
hobby name:游泳
hobby name:跳绳
hobby name:篮球
Stop------------------
Start-----------------
name:张三
list:
hobby name:游泳
hobby name:跳绳
hobby name:篮球
Stop------------------
Start-----------------
name:李四
list:
hobby name:游泳
hobby name:跳绳
hobby name:篮球
hobby name:游戏
Stop------------------
Start-----------------
name:张三
list:
hobby name:游泳
hobby name:跳绳
hobby name:篮球
hobby name:游戏
Stop------------------
从上面结果可以发现:
克隆后的对象stu2与stu的对象内容是相同的,而当去修改stu2的对象的name时不会影响stu的对象的name,但是当用stu2去修 改hobbies内容时,发现stu的对象的hobbies内容发生了变化,为什么会这样呢?
原因在于上面的clone方法中其实不是对对象进行拷贝,而是对对象的引用进行了拷贝,也就是说stu和stu2两个对象中的属性 都指向的是同一块内存空间,当你去修改String类型的时候,其实是两个对象(可以自己去打印比较两个对象的地址),但是当去修改 引用类型的时候就会发生错误,因为指向的是同一块内存空间,stu2修改了hobbies会影响stu的hobbies值.
这就是接下来要说的浅拷贝与深拷贝的区别:
浅拷贝:也称影子拷贝,这份拷贝实际上并不是将原始文档的所有字段都重新构造了一份,而是副本文档的字段引用原始的 字段。
深拷贝:在拷贝对象的时候,对于引用型字段也要采用拷贝的形式,而不是单纯的引用的形式。
注意:通过clone拷贝对象时并不会执行构造函数,因此如果在构造函数中需要一些特殊的初始化操作类型,在使用Cloneable实 现拷贝时,需要注意构造函数不会执行的问题。
上述clone代码修改如下:
@Override
protected Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
stu.name = this.name;
//进行深拷贝
stu.hobbies = (ArrayList<String>)this.hobbies.clone();
return stu;
}
这样再去修改stu2的hobbies就不会再影响stu的hobbies。
总结:原型模式是个非常简单的模式,核心问题就是对原始对象进行拷贝,需要注意的就是深、浅拷贝的问题。在实际开发过程中,建议还是采用深拷贝,避免操作副本时影响原始对象。