设计模式之原型模式

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类

package com.creasy.testjava;

public class Phone{

    private String phone_brand;
    private String phone_number;

    public Phone() {
    }

    public void setBrandAndNumber(String phone_brand, String phone_number) {
        this.phone_brand = phone_brand;
        this.phone_number = phone_number;
    }

    public void Display() {
        System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
    }

}

个人类Person

package com.creasy.testjava;

public class Person implements Cloneable {

    private String name;
    private int age;
    private Phone phone;

    public Person() {
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        return p;
    }

    public void setNameAndAge(String name, int age, Phone phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public void Display() {
        System.out.print("name:" + name + " and age:" + age + " ");
        if (null == phone) {
            System.out.println("has no phone");
        } else {
            phone.Display();
        }
        System.out.println();
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Phone getPhone() {
        return phone;
    }

}

客户端调用:

package com.creasy.testjava;

/**
 * @author laicreasy
 *
 */
public class TestBaseJavaFunction {

    public static void main(String[] args) {
        try {
            Person a = new Person();
            Phone phone = new Phone();
            phone.setBrandAndNumber("iPhone", "1234567890");
            a.setNameAndAge("xiaoli", 18, phone);
            Person b = (Person) a.clone();
            a.Display();
            phone.setBrandAndNumber("Android", "0987654321");
            a.Display();
            b.Display();
            System.out.println(a.getPhone() == b.getPhone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

}

输出结果:

name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
true

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

package com.creasy.testjava;

public class Phone implements Cloneable{

    private String phone_brand;
    private String phone_number;

    public Phone() {
    }

    public void setBrandAndNumber(String phone_brand, String phone_number) {
        this.phone_brand = phone_brand;
        this.phone_number = phone_number;
    }

    public void Display() {
        System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

而Person类中的clone方法改成

@Override
    protected Object clone() throws CloneNotSupportedException {
        Person p = (Person)super.clone();
        p.phone = (Phone)this.phone.clone();
        return p;
    }

再次运行,得到结果

name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
false

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

package com.creasy.testjava;

import java.io.Serializable;

public class Phone implements Serializable{

    private static final long serialVersionUID = 8785807031027660303L;

    private String phone_brand;
    private String phone_number;

    public Phone() {
    }

    public void setBrandAndNumber(String phone_brand, String phone_number) {
        this.phone_brand = phone_brand;
        this.phone_number = phone_number;
    }

    public void Display() {
        System.out.print("phone_brand:" + phone_brand + " and phone_number:" + phone_number);
    }

}

Person类:

package com.creasy.testjava;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Person implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 5767629194216311414L;

    private String name;
    private int age;
    private Phone phone;

    public Person() {
    }

    public Person deepClone() throws IOException, ClassNotFoundException  {
        //将对象写入流中
        ByteArrayOutputStream bao=new  ByteArrayOutputStream();
        ObjectOutputStream oos=new  ObjectOutputStream(bao);
        oos.writeObject(this);
        //将对象从流中取出
        ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
        ObjectInputStream ois=new  ObjectInputStream(bis);
        return  (Person)ois.readObject();
    }

    public void setNameAndAge(String name, int age, Phone phone) {
        this.name = name;
        this.age = age;
        this.phone = phone;
    }

    public void Display() {
        System.out.print("name:" + name + " and age:" + age + " ");
        if (null == phone) {
            System.out.println("has no phone");
        } else {
            phone.Display();
        }
        System.out.println();
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public Phone getPhone() {
        return phone;
    }

}

客户端调用

package com.creasy.testjava;

/**
 * @author laicreasy
 *
 */
public class TestBaseJavaFunction {

    public static void main(String[] args) {
        try {
            Person a = new Person();
            Phone phone = new Phone();
            phone.setBrandAndNumber("iPhone", "1234567890");
            a.setNameAndAge("xiaoli", 18, phone);
            Person b = (Person) a.deepClone();
            a.Display();
            phone.setBrandAndNumber("Android", "0987654321");
            a.Display();
            b.Display();
            System.out.println(a.getPhone() == b.getPhone());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行结果

name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
name:xiaoli and age:18 phone_brand:Android and phone_number:0987654321
name:xiaoli and age:18 phone_brand:iPhone and phone_number:1234567890
false

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

4.Android或者Java中的使用

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

    private int[] mKeys;
    private Object[] mValues;

    @Override
    @SuppressWarnings("unchecked")
    public SparseArray<E> clone() {
        SparseArray<E> clone = null;
        try {
            clone = (SparseArray<E>) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
            /* ignore */
        }
        return clone;
    }

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

public void testSparseArray() {
        SparseArray<Phone> mSparseArray = new SparseArray<Phone>();
        Phone phone1 = new Phone();
        phone1.setBrandAndNumber("SANXING", "123456789");
        Phone phone2 = new Phone();
        phone2.setBrandAndNumber("APPLE", "123456789");
        mSparseArray.put(0, phone1);
        mSparseArray.put(1, phone2);
        //mSparseArray1
        for( int i=0; i < mSparseArray.size(); i++ ) {
            Phone p = mSparseArray.get(i);
            System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
        }
        SparseArray<Phone> mSparseArray2 = mSparseArray.clone();
        for( int i=0; i < mSparseArray2.size(); i++ ) {
            Phone p = mSparseArray.get(i);
            p.setBrandAndNumber(p.getPhone_brand() + "2", p.getPhone_number() + "2");
        }
        //mSparseArray2
        for( int i=0; i < mSparseArray2.size(); i++ ) {
            Phone p = mSparseArray2.get(i);
            System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
        }
        //mSparseArray1
        for( int i=0; i < mSparseArray.size(); i++ ) {
            Phone p = mSparseArray.get(i);
            System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
        }
        //mSparseArray2
        for( int i=0; i < mSparseArray2.size(); i++ ) {
            Phone p = mSparseArray2.get(i);
            System.out.println("phone_brand:" + p.getPhone_brand() + " and phone_number:" + p.getPhone_number());
        }
        System.out.println(mSparseArray == mSparseArray2);
    }

结果
这里写图片描述
由上,可见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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值