原型模式
1.定义:
用原型实例指定创建对象的种类,并且通过复制(克隆)这些原型创建对象。
2.类图:
关键点:
- ProtoType(抽象原型类):它是声明clone方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是实现类。
- ConcreteProtoType(具体原型类):它实现抽象原型类中的clone方法,在方法中返回自己的一个克隆对象。
- Client(客户类):让一个原型对象克隆自身从而创建一个全新的对象。
原型模式主要用于对象的复制,使用场景如下:1.构造函数非常复杂;2.通过new产生一个对象需要非常繁琐的数据准备。
它的核心是ProtoType类,该类需要具备以下条件:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此Prototype类需要将clone方法的作用域修改为public类型。
3.代码实例
假设原型为一个Student
package com.zlfan.clone;
import java.util.Arrays;
public class StudentProtoType implements Cloneable{
private String name;
private int age;
private String []hobby;
public StudentProtoType(){
System.out.println("无参构造函数调用");
}
@Override
protected StudentProtoType clone() throws CloneNotSupportedException {
return (StudentProtoType) super.clone();
}
//get set 方法省略
@Override
public String toString() {
return "StudentProtoType [name=" + name + ",
age=" + age + ", hobby=" + Arrays.toString(hobby) + "]"+super.toString();
}
}
package com.zlfan.clone;
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
StudentProtoType pt = new StudentProtoType();
pt.setHobby(new String []{"跑步","篮球","足球"});
pt.setName("zlfan");
pt.setAge(18);
StudentProtoType pt1 = pt.clone();
pt1.setAge(20);//设置年龄
String[] hobby = pt1.getHobby();
hobby[0]="打游戏";
System.out.println(pt);
System.out.println(pt1);
}
}
运行结果如下:
无参构造函数调用
StudentProtoType [name=zlfan, age=18, hobby=[打游戏, 篮球]][email protected]
StudentProtoType [name=zlfan, age=20, hobby=[打游戏, 篮球]][email protected]
从运行结果中,我们可以看到,无参构造函数只调用了一次,却产生了两个对象。值得注意的是,使用clone方法创建的新对象的构造函数是不会被执行的,也就是说会绕过任何构造函数(有参和无参),因为clone方法的原理是从堆内存中以二进制流的方式进行拷贝,直接分配一块新内存。
年龄与爱好都修改了,而输出结果中为什么两者年龄不一样,hobby却一样的呢?我们需要先来了解一下浅拷贝与深拷贝(浅克隆与深克隆)学过c++的同学可忽视以下内容。
4.浅克隆与深克隆
浅克隆:
对值类型的成员变量进行值的复制,对引用数据类型的成员变量只复制引用,不复制引用的对象
程序中,age是值类型的成员变量,存储在栈中,复制时,值也可以一并复制过去。而hobby则是引用类型的成员变量,栈中只存了它的引用地址,引用类型变量实际上存在堆中的。复制的时候,只是把多个对象指向它。如下图:
当对hobby进行改变时,两者自然都改变了。
深拷贝:
对值类型的成员变量进行值的复制,对引用数据类型的成员变量也进行引用对象的复制。
上图于是变成了这样:
两个对象各分配一个存储空间,不再指向同一个空间。两者互不干扰。
Java中默认的是浅克隆,我们需要对clone方法做一点改造就能变成深克隆了,把hobby也给克隆了。
protected StudentProtoType clone() throws CloneNotSupportedException {
StudentProtoType pt = (StudentProtoType) super.clone();
pt.hobby = pt.getHobby().clone();
return pt;
}
运行结果:
无参构造函数调用
StudentProtoType [name=zlfan, age=18, hobby=[跑步, 篮球]][email protected]
StudentProtoType [name=zlfan, age=20, hobby=[打游戏, 篮球]][email protected]
5.原型模式的优缺点
优点:1.原型模式性能比直接new一个对象性能高;
2.避免构造函数的约束。
缺点:1.只有当通过new构造对象较为耗时或者说成名较高时,通过clone方法才能获得效率上的提高;
2.必须实现Cloneable接口。
6.原型模式适用性
Prototype模式适用于:
- 当一个系统应该独立于它的产品创建、构成和表示时。
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载。
- 为了避免创建一个与产品类层次平行的工厂类层次时。
- 当一个类的实例只能有几个不同的状态组合中的一种时。建立相应数目的原型并克隆它们,可能比每次用合适的状态手工实例化该类更方便一些。
如有错误,欢迎指正