设计模式之原型模式

1.定义

使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 
面向对象语言中,拷贝分为两类: 
1. 浅拷贝 
对于基础数据类型(如Java中short/int/long/float/double/char/byte/boolean),直接复制数值,对于引用数据类型,则直接复制引用,修改此引用指向的对象会对原引用对象造成影响 
2. 深拷贝 
对于基础数据类型,直接复制数值,对于引用数据类型,则拷贝一份引用对象至分配到的新内存中,修改此对象对原引用对象不会有影响

2.UML图

Client:客户端用户,提出创建克隆对象的请求 
Prototype:原型接口,标识对象是否可以克隆 
ConcretePrototye:具体实例,实现了原型接口的类,通过clone()方法可返回自身的一个副本

3.实现

Java中的类都继承自 java.lang.Object。Object 类提供有一个 clone() 方法,可以将一个 Java 对象复制一份。因此在 Java 中可以直接使用 Object 提供的 clone() 方法来实现对象的克隆。 
需要注意的是能够实现克隆的 Java 类都必须实现一个标识接口 Cloneable,表示这个 Java 类支持被复制。如果一个类没有实现这个接口但是调用了 clone() 方法,Java 编译器将抛出一个 CloneNotSupportedException 异常。 
以下以持有手机的个人的克隆为示例 
1. 简单形式 
个人所持有的手机Phone类

 1 package com.creasy.testjava;
 2 
 3 public class Phone{
 4 
 5     private String phone_brand;
 6     private String phone_number;
 7 
 8     public Phone() {
 9     }
10 
11     public void setBrandAndNumber(String phone_brand, String phone_number) {
12         this.phone_brand = phone_brand;
13         this.phone_number = phone_number;
14     }
15 
16     public void Display() {
17         System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
18     }
19 
20 }

个人类Person

 1 package com.creasy.testjava;
 2 
 3 public class Person implements Cloneable {
 4 
 5     private String name;
 6     private int age;
 7     private Phone phone;
 8 
 9     public Person() {
10     }
11 
12     @Override
13     protected Object clone() throws CloneNotSupportedException {
14         Person p = (Person)super.clone();
15         return p;
16     }
17 
18     public void setNameAndAge(String name, int age, Phone phone) {
19         this.name = name;
20         this.age = age;
21         this.phone = phone;
22     }
23 
24     public void Display() {
25         System.out.print("name:" + name + " and age:" + age + " ");
26         if (null == phone) {
27             System.out.println("has no phone");
28         } else {
29             phone.Display();
30         }
31         System.out.println();
32     }
33 
34     public String getName() {
35         return name;
36     }
37 
38     public int getAge() {
39         return age;
40     }
41 
42     public Phone getPhone() {
43         return phone;
44     }
45 
46 }

客户端调用:

 1 package com.creasy.testjava;
 2 
 3 /**
 4  * @author laicreasy
 5  *
 6  */
 7 public class TestBaseJavaFunction {
 8 
 9     public static void main(String[] args) {
10         try {
11             Person a = new Person();
12             Phone phone = new Phone();
13             phone.setBrandAndNumber("iPhone", "1234567890");
14             a.setNameAndAge("xiaoli", 18, phone);
15             Person b = (Person) a.clone();
16             a.Display();
17             phone.setBrandAndNumber("Android", "0987654321");
18             a.Display();
19             b.Display();
20             System.out.println(a.getPhone() == b.getPhone());
21         } catch (CloneNotSupportedException e) {
22             e.printStackTrace();
23         }
24     }
25 
26 }

输出结果:

1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
2 name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
3 name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
4 true

由结果可以看出,Person中Phone的克隆只是简单的复制了引用给到新对象。如果Phone类改成

 1 package com.creasy.testjava;
 2 
 3 public class Phone implements Cloneable{
 4 
 5     private String phone_brand;
 6     private String phone_number;
 7 
 8     public Phone() {
 9     }
10 
11     public void setBrandAndNumber(String phone_brand, String phone_number) {
12         this.phone_brand = phone_brand;
13         this.phone_number = phone_number;
14     }
15 
16     public void Display() {
17         System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
18     }
19 
20     @Override
21     protected Object clone() throws CloneNotSupportedException {
22         return super.clone();
23     }
24 }

而Person类中的clone方法改成

1 而Person类中的clone方法改成
2 
3 @Override
4     protected Object clone() throws CloneNotSupportedException {
5         Person p = (Person)super.clone();
6         p.phone = (Phone)this.phone.clone();
7         return p;
8     }

再次运行,得到结果

1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
2 name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
3 name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
4 false

由结果可以看到,此时则是深层复制,a和b中的phone指向的是不同的对象的 
2. 实现Serializable形式 
序列化的过程就是把数据从内存中写入流中,反序列化则是从流中把数据再写入新内存中,通过序列化和反序列化则可以实现对象的深层复制 
Phone类

 1 package com.creasy.testjava;
 2 
 3 import java.io.Serializable;
 4 
 5 public class Phone implements Serializable{
 6 
 7     private static final long serialVersionUID = 8785807031027660303L;
 8 
 9     private String phone_brand;
10     private String phone_number;
11 
12     public Phone() {
13     }
14 
15     public void setBrandAndNumber(String phone_brand, String phone_number) {
16         this.phone_brand = phone_brand;
17         this.phone_number = phone_number;
18     }
19 
20     public void Display() {
21         System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
22     }
23 
24 }

Person类:

 1 package com.creasy.testjava;
 2 
 3 import java.io.ByteArrayInputStream;
 4 import java.io.ByteArrayOutputStream;
 5 import java.io.IOException;
 6 import java.io.ObjectInputStream;
 7 import java.io.ObjectOutputStream;
 8 import java.io.Serializable;
 9 
10 public class Person implements Serializable {
11 
12     /**
13      * 
14      */
15     private static final long serialVersionUID = 5767629194216311414L;
16 
17     private String name;
18     private int age;
19     private Phone phone;
20 
21     public Person() {
22     }
23 
24     public Person deepClone() throws IOException, ClassNotFoundException  {
25         //将对象写入流中
26         ByteArrayOutputStream bao=new  ByteArrayOutputStream();
27         ObjectOutputStream oos=new  ObjectOutputStream(bao);
28         oos.writeObject(this);
29         //将对象从流中取出
30         ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
31         ObjectInputStream ois=new  ObjectInputStream(bis);
32         return  (Person)ois.readObject();
33     }
34 
35     public void setNameAndAge(String name, int age, Phone phone) {
36         this.name = name;
37         this.age = age;
38         this.phone = phone;
39     }
40 
41     public void Display() {
42         System.out.print("name:" + name + " and age:" + age + " ");
43         if (null == phone) {
44             System.out.println("has no phone");
45         } else {
46             phone.Display();
47         }
48         System.out.println();
49     }
50 
51     public String getName() {
52         return name;
53     }
54 
55     public int getAge() {
56         return age;
57     }
58 
59     public Phone getPhone() {
60         return phone;
61     }
62 
63 }

客户端调用

 1 package com.creasy.testjava;
 2 
 3 /**
 4  * @author laicreasy
 5  *
 6  */
 7 public class TestBaseJavaFunction {
 8 
 9     public static void main(String[] args) {
10         try {
11             Person a = new Person();
12             Phone phone = new Phone();
13             phone.setBrandAndNumber("iPhone", "1234567890");
14             a.setNameAndAge("xiaoli", 18, phone);
15             Person b = (Person) a.deepClone();
16             a.Display();
17             phone.setBrandAndNumber("Android", "0987654321");
18             a.Display();
19             b.Display();
20             System.out.println(a.getPhone() == b.getPhone());
21         } catch (Exception e) {
22             e.printStackTrace();
23         }
24     }
25 
26 }

运行结果

1 name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
2 name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
3 name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
4 false

由结果可以看出,对应a和对象b是不同的对象,并且他们各自持有不同的phone对象 
通过序列化及反序列化进行深层复制的前提是对象以及对象内部所有引用到的对象都是可序列化的,否则,需要声明不可序列化的对象成transient,从而将之排除在复制过程之外。关于transient关键字的描述,可参考:transient

4.Android或者Java中的使用

Android中,存储Integer和Object的键值对类SparseArray实现了Cloneable,直接看其clone源码,如下:

 1 private int[] mKeys;
 2     private Object[] mValues;
 3 
 4     @Override
 5     @SuppressWarnings("unchecked")
 6     public SparseArray<E> clone() {
 7         SparseArray<E> clone = null;
 8         try {
 9             clone = (SparseArray<E>) super.clone();
10             clone.mKeys = mKeys.clone();
11             clone.mValues = mValues.clone();
12         } catch (CloneNotSupportedException cnse) {
13             /* ignore */
14         }
15         return clone;
16     }

使用SparseArray测试,代码及结果如下

 1 public void testSparseArray() {
 2         SparseArray<Phone> mSparseArray = new SparseArray<Phone>();
 3         Phone phone1 = new Phone();
 4         phone1.setBrandAndNumber("SANXING", "123456789");
 5         Phone phone2 = new Phone();
 6         phone2.setBrandAndNumber("APPLE", "123456789");
 7         mSparseArray.put(0, phone1);
 8         mSparseArray.put(1, phone2);
 9         //mSparseArray1
10         for( int i=0; i < mSparseArray.size(); i++ ) {
11             Phone p = mSparseArray.get(i);
12             System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
13         }
14         SparseArray<Phone> mSparseArray2 = mSparseArray.clone();
15         for( int i=0; i < mSparseArray2.size(); i++ ) {
16             Phone p = mSparseArray.get(i);
17             p.setBrandAndNumber(p.getPhone_brand() + "2", p.getPhone_number() + "2");
18         }
19         //mSparseArray2
20         for( int i=0; i < mSparseArray2.size(); i++ ) {
21             Phone p = mSparseArray2.get(i);
22             System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
23         }
24         //mSparseArray1
25         for( int i=0; i < mSparseArray.size(); i++ ) {
26             Phone p = mSparseArray.get(i);
27             System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
28         }
29         //mSparseArray2
30         for( int i=0; i < mSparseArray2.size(); i++ ) {
31             Phone p = mSparseArray2.get(i);
32             System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
33         }
34         System.out.println(mSparseArray == mSparseArray2);
35     }

结果 

由上,可见SparseArray中clone方法执行的是浅层复制功能(实际上SparseArray中存储的Object为数组,而数组的clone执行的是浅层复制功能)

5.使用场景

(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的 CPU 资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得 
(2)当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候,不适合使用原型模式

参考博客: 

http://wiki.jikexueyuan.com/project/design-pattern-creation/protorype-four.html 
http://blog.csdn.net/bboyfeiyu/article/details/38398475 
http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.html

原文地址:https://www.cnblogs.com/creasylai19/p/4882059.html