目录
1.原型模式
1.1 概念
在系统开发中,有时候有些对象需要被频繁创建,原型模式(Prototype Pattern)原型模式是一种对象创建型模式,通过给出一个原型对象来指明所要创建的对象的类型,然后通过拷贝复制这个原型对象的办法创建出更多的新对象。 原型模式允许一个对象再创建另外一个可定制的对象,而无需知道任何创建的细节。 1.2 工作原理
通过将一个原型对象传给那个要发起创建的对象,这个要发起创建的对象通过请求原型对象来复制原型对象自己来实现创建过程。 1.3 类图
类图如下所示:
1.4 角色
类图中的角色如下所示:
Prototype 表示抽象原型类,它定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,
可以是抽象类,也可以是接口。
ConcretePrototypeA 表示具体的原型类A,实现具体的克隆方法,返回自己的一个克隆对象。 ConcretePrototypeB 表示具体的原型类B,实现具体的克隆方法,返回自己的一个克隆对象。 Client 表示客户端,让一个原型对象克隆自身而创建一个新的对象。 1.5 原型分类
原型模式分为两种,分别为浅克隆和深克隆。两者的区别是:是否支持引用对象的复制,从而造成原始对象和副本是否引用同一个对象的问题(可能会导致克隆对象改变了引用的成员变量的值,原始对象里面的引用对象的值也会被改变)。
1.5.1 浅克隆
浅克隆实现 Cloneable,重写其clone()方法,浅克隆仅仅复制所考虑的对象。
对引用类型的成员变量只复制引用,并不复制引用的对象,只是将引用对象的地址复制一份给克隆对象。对于数组、容器对象、引用对象等都不会拷贝,如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝 对值类型的成员变量进行值的复制。也就是说其中的成员对象并不复制。 1.5.2 深克隆
深克隆是通过实现 Serializable 读取二进制流。 深克隆中,除了对象本身被复制外,对象包含的引用类型的成员变量也被复制,即成员对象也将被复制。
1.5.3 完全克隆
在包括上面两者共同点的基础上把对象间接引用的对象也进行拷贝。这是最彻底的一种拷贝。通常先使对象序列化,实现Serializable接口 然后将对象写进二进制流里 再从二进制流里读出新对象。
2.原型模式实现原理
原型模式主要用于对象的复制,它的核心就是类图中的原型类Prototype。Protype类需要具备以下两个条件:实现Cloneable接口,重写Object类中的clone方法。
2.1 实现Cloneable接口
在 java语言中有一个Cloneable接口,它的作用只有一个,就是在运行时通过虚拟机可以安全的再实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则运行时会抛出CloneNotSupportedException异常
2.2 重写Object类中的clone方法
在java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般都类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
2.3 案例实现
2.3.1 浅克隆
浅克隆原型类:
/** * 简历原型类 * @author Administrator */ public class Resume implements Cloneable{ private String name; //名字 private int age; //年龄 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Resume clone() { Resume resume = null; try { //浅克隆 resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } }
具体实现类:
public class ConcretePrototype extends Resumes{ public void show() { System.out.println("克隆对象"); } }
客户端:
public class Client { public static void main(String[] args) { ConcretePrototype cp1 = new ConcretePrototype(); cp1.setName("张三"); cp1.setAge(1); cp1.setHobby(new String[] {"羽毛球","篮球","乒乓球"}); System.out.println(cp1); //克隆对象 ConcretePrototype cp2 = (ConcretePrototype) cp1.clone(); //将克隆的新对象的引用类型数组值改变 String[] hobby = cp2.getHobby(); hobby[0] = "足球"; cp2.setHobby(hobby); //输出克隆的新对象 System.out.println(cp2); //输出原始对象,查看两个对象的引用类似数组中的值是否一致 System.out.println(cp1); } }
效果:
Resumes [name=张三, age=1, hobby=[羽毛球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@70dea4e Resumes [name=张三, age=1, hobby=[足球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@5c647e05 Resumes [name=张三, age=1, hobby=[足球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@70dea4e
总结:可以看出,浅克隆并没有拷贝一份引用类型数组,当克隆对象改变数组里面的值,其原型对象的数组里面的值也被改变了。
2.3.2 深克隆
深克隆原型类:
/** * 简历原型类 * @author Administrator */ public class Resumes implements Cloneable{ private String name; //名字 private int age; //年龄 private String[] hobby; //爱好 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String[] getHobby() { return hobby; } public void setHobby(String[] hobby) { this.hobby = hobby; } @Override public String toString() { return "Resumes [name=" + name + ", age=" + age + ", hobby=" + Arrays.toString(hobby) + "]" + " >>>地址"+super.toString(); } public Resumes clone() { Resumes resume = null; try { resume = (Resumes) super.clone(); //深克隆 resume.hobby =resume.getHobby().clone(); } catch (Exception e) { e.printStackTrace(); } return resume; } }
具体实现类:
public class ConcretePrototype extends Resumes{ public void show() { System.out.println("克隆对象"); } }
客户端:
public class Client { public static void main(String[] args) { ConcretePrototype cp1 = new ConcretePrototype(); cp1.setName("张三"); cp1.setAge(1); cp1.setHobby(new String[] {"羽毛球","篮球","乒乓球"}); System.out.println(cp1); //克隆对象 ConcretePrototype cp2 = (ConcretePrototype) cp1.clone(); //将克隆的新对象的引用类型数组值改变 String[] hobby = cp2.getHobby(); hobby[0] = "足球"; cp2.setHobby(hobby); //输出克隆的新对象 System.out.println(cp2); //输出原始对象,查看两个对象的引用类似数组中的值是否一致 System.out.println(cp1); } }
结果:
Resumes [name=张三, age=1, hobby=[羽毛球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@70dea4e Resumes [name=张三, age=1, hobby=[足球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@5c647e05 Resumes [name=张三, age=1, hobby=[羽毛球, 篮球, 乒乓球]] >>>地址com.prototype_pattern.ConcretePrototype@70dea4e
总结:可以看出,克隆对象将引用类型数组拷贝了一份,然后改变其值,原始对象的引用类型数组中的值没有发生改变。
3.原型模式优缺点
3.1 优点
性能提高。 逃避构造函数的约束。 3.2 缺点
每个类必须配备一个克隆方法,在对已有系统进行改造时难度较大。 实现深度克隆需要编写较为复杂的代码。 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
4.原型模式应用场景
原型模式有以下应用场景:
1.当我们的对象类型不是开始就能确定的,而这个类型是在运行期间确定的话,那么我们通过这个类型的对象克隆出一个新的对象比较容易。 2.有时候,我们需要一个对象在某个状态下的副本,此时,使用原型模式是好的选择。 3.原型模式很少单独出现,经常与其他模式混用。,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。 4.资源优化场景。 5.类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 6.一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7.通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5.注意事项
1.对象拷贝时构造函数并没有被执行,这点可以从原理上来分析:Object类的clone方法的原理是从内存中(具体的说是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块。 2.必须实现 Cloneable 接口,否则报CloneNotSupportedException异常。