详谈Java设计模式之原型(Prototype)模式

来源:互联网 发布:华泰交易软件下载 编辑:程序博客网 时间:2024/06/18 15:22

一、概念

原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。

二、复制原理

在Java中,原型模式的实现主要通过原型对象(Prototype类)的复制来完成。Prototype类需具备如下条件。

  • 实现Cloneable接口。其实Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,它的作用只有一个,就是在运行时通知虚拟机可以在该类上使用clone()方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
  • 重写clone()方法。准确来说是重写定义了一个克隆方法,而不是重写,因为该克隆方法的名字可以自定义。该方法的作用是返回自身对象的一个拷贝,其作用域通常定义为public类型。

根据Prototype类中clone()方法的实现方式的不同,可分为浅复制深复制。主要区别是,浅复制将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型变量,指向的还是原来的对象;深复制将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的。也就是说,深复制进行了完全彻底的复制,而浅复制不彻底。下面将通过一个具体的例子来说明深浅复制的实现过程,具体代码如下。

1、原型(Prototype)类

Person.java

package com.yushen.design.prototype;import java.util.ArrayList;/** * 原型(prototype)类 */public class Person implements Cloneable {private static final long serialVersionUID = 1L;//姓名--基本数据类型private String name;//年龄--基本数据类型private Integer age;//朋友--引用类型private ArrayList<String> friends;public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public ArrayList<String> getFriends() {return friends;}public void setFriends(ArrayList<String> friends) {this.friends = friends;}//重写clone()方法,浅复制public Person shallowClone() throws CloneNotSupportedException {return (Person)super.clone();}//重写clone()方法,深复制public Person deepClone() throws CloneNotSupportedException {Person clonePerson = (Person)super.clone();clonePerson.friends = (ArrayList<String>) this.friends.clone();return clonePerson;}@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + ", friends=" + friends + "]";}}

由上述代码可知,浅复制就是调用父类(Object类)的clone方法实现的,Object的clone()方法是个本地方法,其实现过程此处不再深究。JDK的源码中对clone()方法做了注释,如图1所示,红色部分是我标出的一些关键解读。


图1

由注释1可知,克隆对象和原型对象是独立的两个对象,也就是不同的对象。由注释2可知,那些mutable(引用型)的对象需要单独拷贝来替换掉原来的引用,而那些primitive(基本数据类型)或者reference to immutable(不可改变的引用对象,比如有finalstatic修饰的引用型变量),不需要单独进行克隆操作。注释3则说明了Object的clone()方法是一种浅复制。下面我们将对深浅复制进行测试,具体代码如下。

2、测试类

MainClass.java

package com.yushen.designmodel.prototype;import java.util.ArrayList;/** * 测试类 */public class MainClass {public static void main(String[] args) throws CloneNotSupportedException {//创建一个原型对象Person person = new Person();person.setName("张三");person.setAge(25);ArrayList<String> friends = new ArrayList<>();friends.add("刘德华");friends.add("张学友");person.setFriends(friends);System.out.println("Prototype-->" + person);//克隆一个原型对象--浅复制System.out.println("浅复制------------");Person clonePerson1 = person.shallowClone();System.out.println("clonePerson1-->" + clonePerson1);System.out.println("克隆对象与原型对象是否为同一个引用");System.out.println(person == clonePerson1);System.out.println("克隆对象中的引用类型数据与原型对象中的引用类型数据是否为同一个引用");System.out.println(person.getFriends() == clonePerson1.getFriends());//克隆一个原型对象--深复制System.out.println("深复制------------");Person clonePerson2 = person.deepClone();System.out.println("clonePerson2-->" + clonePerson2);System.out.println("克隆对象与原型对象是否为同一个引用");System.out.println(person == clonePerson2);System.out.println("克隆对象中的引用类型数据与原型对象中的引用类型数据是否为同一个引用");System.out.println(person.getFriends() == clonePerson2.getFriends());}}

3、运行结果

Prototype-->Person [name=张三, age=25, friends=[刘德华, 张学友]]浅复制------------clonePerson1-->Person [name=张三, age=25, friends=[刘德华, 张学友]]克隆对象与原型对象是否为同一个引用false克隆对象中的引用类型数据与原型对象中的引用类型数据是否为同一个引用true深复制------------clonePerson2-->Person [name=张三, age=25, friends=[刘德华, 张学友]]克隆对象与原型对象是否为同一个引用false克隆对象中的引用类型数据与原型对象中的引用类型数据是否为同一个引用false

由上述运行结果可知,不管是深复制得到的克隆对象还是浅复制得到的克隆对象,与原型对象相比,是不同的对象(判断结果为false)。但是在浅复制里,克隆对象中的引用类型数据与原型对象中的引用类型数据是同一个引用(判断结果为true),而在深复制里,克隆对象中的引用类型数据与原型对象中的引用类型数据是不同的引用(判断结果为false)。由此,之前的结论得到再次证明。

深度复制除了利用Object的clone()加上引用类型单独拷贝的方法外,还可以采取数据流的形式进行克隆,只需将Person.java中的deepClone()方法修改如下。

public Person deepClone() throws CloneNotSupportedException {Person clonePerson = null;        try {        //将对象写到流里ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);//从流里读回来ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);clonePerson = (Person) ois.readObject();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return clonePerson;}

三、Java应用

由上述代码可知,在我们把Person中friends引用对象(ArrayList对象)进行单独克隆的时候,其实调用的是ArrayList本身的clone()方法,通过查看源代码可以发现,该clone()方法也重写了Object的clone()方法。由此说明ArrayList也是通过原型(Prototype)模式,来达到对自身进行复制的目的。下面我贴出了ArrayList.clone()方法的部分源代码的截图。

图2

由图2可知,ArrayList.clone()方法也是先调用父类(Object类)的克隆方法进行浅复制,然后再对引用对象elementData(Object[]类型)进单独克隆,从而达到深复制的目的。我们继续查看Arrays.copyof()方法,如图3所示。


图3

Arrays.copyof()方法返回的copy对象是新创建的对象,具体的实现过程是,先new一个T[]类型的copy对象,然后调用系统System.arraycopy()方法将原数组中的元素复制到该copy对象中。Arrays.copyof()方法的源代码如下。


图4

由图4可知,Arrays.copyof()方法是一个本地方法,它通过直接操作内存中的二进制流来实现的,此处不再深究。

四、总结

当我们需要重复地创建相似对象时可以考虑使用原型模式。尤其是对象比较复杂的时候,使用原型模式创建对象比直接new一个对象在性能上要好的多,因为它所调用的Object.clone方法是一个本地方法,它直接操作内存中的二进制流,自然不会调用自身的构造方法,也不会递归的调用父类的构造方法,从而提高整体性能。此外,原型模式的复制方式分为浅复制和深复制两种,浅复制通过调用父类(Object类)的clone()方法实现,该方法只会对基本数据类型或者进拷贝,数组等引用类型不会拷贝。要想实现深复制,则需要单独对引用类型的数据进行拷贝。

阅读全文
1 0
原创粉丝点击