原型模式(Prototype Pattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

原型模式利用的是克隆的原理,创建新的对象,JDK提供的Cloneable 和JSON、springUtil里面的克隆都是一般浅克隆,与之对应的还有深克隆

1、浅克隆

  浅克隆也是穿件一个新的对象,不过该对象的属性值是被克隆对象的,如果修改被克隆对象,后者跟着修改。

下面我们用Cloneable写一个简单的浅克隆

import java.util.List;
/**
 * @Description TODO
 * @Author Bert
 * @Date 2019\6\10 0010
 */
public class ShallowClone implements Cloneable{

    private String name;
    private int age;
    private List<String> hobbies;

    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 List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    //实现 Cloneable 的 clone 方法  属于浅克隆
    @Override
    public ShallowClone clone() {
        ShallowClone shallowClone = null;
        try {
            shallowClone = (ShallowClone)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return shallowClone;
    }

    //手写 浅克隆 方法
    public ShallowClone spellClone(){
        ShallowClone shallowClone = new ShallowClone();
        shallowClone.setName(this.name);
        shallowClone.setAge(this.age);
        shallowClone.setHobbies(this.hobbies);
        return shallowClone;
    }
}

  测试代码:

public static void main(String[] args) {

        ShallowClone shallowClone = new ShallowClone();
        shallowClone.setName("ZhangSan");
        shallowClone.setAge(21);
        List<String> list = new ArrayList<>();
        list.add("play game");
        list.add("Listen music");
        shallowClone.setHobbies(list);
        
        //调用Cloneable 的clone方法
        ShallowClone clone1 = shallowClone.clone();
        //添加喜好
        shallowClone.getHobbies().add("fitness");
        //调用手写的浅克隆方法
        ShallowClone clone2 = shallowClone.spellClone();

        System.out.println(shallowClone == clone1);//判读是否为同一个对象
        System.out.println(shallowClone == clone2);//判读是否为同一个对象

        System.out.println("克隆对象中引用类型地址:"+shallowClone.getHobbies());
        System.out.println("克隆对象中引用类型地址:"+clone1.getHobbies());
        System.out.println("克隆对象中引用类型地址:"+clone2.getHobbies());
    }

  运行结果:

false
false
克隆对象中引用类型地址:[play game, Listen music, fitness]
克隆对象中引用类型地址:[play game, Listen music, fitness]
克隆对象中引用类型地址:[play game, Listen music, fitness]

  从运行结果我们可以看出,克隆对象和原来的对象不是同一个,但对象的属性完全一样,即使改变了其中一个,其他的也会跟着改变。

  通过上面spellClone() 方法我们可以看出,浅克隆的整个过程就是,创建一个新的对象,然后新对象的每个值都是由原对象的值,通过 = 进行赋值;

    (1)拷贝后获取的是一个独立的对象,和原对象拥有不同的内存地址;

    (2)基本元素类型,两者是隔离的,int, Integer, long, Long, char, Charset, byte,Byte, boolean, Boolean, float,Float, double, Double, String

    (3)非基本数据类型(如基本容器,其他对象等),只是拷贝了一份引用出去了,实际指向的依然是同一份,例如上的list<String>

  一句话总结就是:基本数据类型是值赋值;非基本的就是引用赋值。

2、深克隆

  创建一个全新的对象,新的对象内部所有的成员也都是全新的,只是初始化的值已经由被克隆的对象确定,但是他们是两个完全独立的对象,修改是隔离,互不影响。

下面我们来写一个简单的深克隆

import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
 * @Description TODO
 * @Author Bert
 * @Date 2019\6\10 0010
 */
public class DeepClone implements Serializable{
    private String name;
    private int age;
    private List<String> hobbies;

    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 List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    // 深克隆 方法 1
    public DeepClone deepClone(){
        DeepClone deepClone = new DeepClone();
        deepClone.setName(this.name);
        deepClone.setAge(this.age);
        //基本数据类型 重新创建
        if(null != this.hobbies){
            deepClone.setHobbies(new ArrayList<>(this.hobbies));
        }
        return deepClone;
    }

    //深克隆 2 通过 byte字节码 ,这种方式需要 实现 Serializable
    public DeepClone deepCloneByte() {
        DeepClone dClone = null;
        ByteArrayOutputStream bys = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bys);
            oos.writeObject(this);

            ByteArrayInputStream bis = new ByteArrayInputStream(bys.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            dClone = (DeepClone)ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dClone;
    }

}

  测试代码:

public static void main(String[] args) {
        DeepClone deepClone = new DeepClone();
        deepClone.setName("ZhangSan");
        deepClone.setAge(21);
        List<String> list = new ArrayList<>();
        list.add("play game");
        list.add("Listen music");
        deepClone.setHobbies(list);
        //调用第一种方式,并修改值
        DeepClone deepClone1 = deepClone.deepClone();
        deepClone1.getHobbies().add("fitness");
        deepClone1.setAge(22);
        //调用第二种方式,并修改值
        DeepClone deepClone2 = deepClone.deepCloneByte();
        deepClone2.getHobbies().add("Climbing mountain");

        System.out.println(deepClone == deepClone1);
        System.out.println(deepClone == deepClone2);
        System.out.println("克隆对象中引用类型地址:"+deepClone.getHobbies());
        System.out.println("克隆对象中引用类型地址:"+deepClone1.getHobbies());
        System.out.println("克隆对象中引用类型地址:"+deepClone2.getHobbies());
    }

  运行结果:

false
false
克隆对象中引用类型地址:[play game, Listen music]
克隆对象中引用类型地址:[play game, Listen music, fitness]
克隆对象中引用类型地址:[play game, Listen music, Climbing mountain]

  以上可以看出深克隆的对象之间修改完全不受影响。

3、原型模式运用场景

  (1)类初始化消耗资源较多。

  (2)new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)

  (3)构造函数比较复杂。

  (4)循环体中生产大量对象时。

在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用 的 JSON.parseObject()也是一种原型模式。

4、解决深克隆破坏单例模式

  如果我们的被克隆对象是单例模式,深克隆就得破坏单例模式。

  解决方案:禁止深克隆。一是不实现Cloneable; 二是实现Cloneable ,在clone方法中返回我们的单例。

 

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

 

  Cloneable 源码分析:

  以ArryList 为例,ArrayList 就实现了 Cloneable 接口

/**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }