原型模式(Prototype Pattern) 以及clone()方法的源码

1.应用场景

原型模式( Prototype Patte rn )是指原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式主要适用于以下场景 :
(1)类初始化消耗资源较多。
(2)使用 new 生成一个对象需要非常烦琐的过程(数据准备、访问权限 等)。
(3)构 造函数比 较复杂。
(4)在循环体中产生大量对象。
 
原型模式的类结构图:
原型模式(Prototype Pattern) 以及clone()方法的源码_第1张图片

2.潜克隆

标准的原型模式代码应该这样设计,先创建原型Prototype接口:

package nju.java.pattern.prototype_pattern;

public interface Prototype {
    Prototype clone();
}

具体需要克隆的类ConcretePrototypeA:

package nju.java.pattern.prototype_pattern;

import java.util.List;

public class ConcretePrototypeA implements Prototype{
    private int age;
    private String name;
    private List hobbbies;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List getHobbbies() {
        return hobbbies;
    }

    public void setHobbbies(List hobbbies) {
        this.hobbbies = hobbbies;
    }

    @Override
    public ConcretePrototypeA clone() {
        ConcretePrototypeA concretePrototypeA=new ConcretePrototypeA();
        concretePrototypeA.setAge(this.age);
        concretePrototypeA.setName(this.name);
        concretePrototypeA.setHobbbies(this.hobbbies);
        return concretePrototypeA;
    }
}

Client类:

package nju.java.pattern.prototype_pattern;

public class Client {
    private Prototype prototype;
    public Client(Prototype prototype){
        this.prototype=prototype;
    }
    public Prototype startClone(Prototype concretePrototype){
        return (Prototype)concretePrototype.clone();
    }
}

测试代码如下:

package nju.java.pattern.prototype_pattern;

import java.util.ArrayList;

public class PrototypeTest {
    public static void main(String[] args) {
        //创建一个具体的需要克隆的对象
        ConcretePrototypeA concretePrototype=new ConcretePrototypeA();
        //随便填写点属性
        concretePrototype.setAge(22);
        concretePrototype.setName("原型");
        concretePrototype.setHobbbies(new ArrayList());
        System.out.println(concretePrototype);

        //创建Client对象 开始克隆
        Client client=new Client(concretePrototype);
        ConcretePrototypeA concretePrototypeClone=(ConcretePrototypeA)client.startClone(concretePrototype);
        System.out.println(concretePrototypeClone);

        //测试其他的
        System.out.println("克隆对象中的引用类型地址值:"+concretePrototypeClone.getHobbbies());
        System.out.println("原对象中的引用类型地地址值:"+concretePrototype.getHobbbies());
        System.out.println("对象地址比较:"+(concretePrototype.getHobbbies()==concretePrototypeClone.getHobbbies()));
    }
}

运行结果如下:

原型模式(Prototype Pattern) 以及clone()方法的源码_第2张图片

可以看到hobbies的引用地址相同,意味着复制的不是值,而是引用的地址。如果我们修改任意一个对象的属性值,则concretePrototype和concretePrototypeClone的hobbies都会改变,这就是我们常说的潜克隆。潜克隆只是完整复制了值类型数据,没有赋值引用对象,换句话说,所有的引用对象仍然指向原来的对象,显然这不是我们想要的结果。

3.深克隆

书中对于深克隆的介绍换了另一个场景,一个我们中国人熟知的故事。齐天大圣与他的金箍棒,齐天大圣可以七十二变,也可以拔一根毫毛吹出千万猴子猴孙,他的武器金箍棒可大可小,伸缩自如。这就是我们耳熟能详的原型模式经典体现。

创建原型猴子类Monkey:

package nju.java.pattern.prototype_pattern;

import java.util.Date;

public class Monkey {
    public int height;
    public int weight;
    public Date birthday;
}

创建引用对象金箍棒类Jingubang:

package nju.java.pattern.prototype_pattern;

import java.io.Serializable;

public class Jingubang implements Serializable {
    public float h=100;
    public float d=10;
    public void big(){
        this.d*=2;
        this.h*=2;
    }
    public void small(){
        this.d/=2;
        this.h/=2;
    }
}

具体对象类齐天大圣QiTianDaSheng:

package nju.java.pattern.prototype_pattern;

import java.io.*;
import java.util.Date;

public class QiTianDaSheng extends Monkey implements Cloneable, Serializable {
    public Jingubang jingubang;
    public QiTianDaSheng(){
        this.birthday=new Date();
        this.jingubang=new Jingubang();
    }

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

    private Object deepClone() {
        try{
            ByteArrayOutputStream bos=new ByteArrayOutputStream();
            ObjectOutputStream oos=new ObjectOutputStream(bos);
            oos.writeObject(this);

            ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois=new ObjectInputStream(bis);

            QiTianDaSheng copy=(QiTianDaSheng)ois.readObject();
            copy.birthday=new Date();
            return copy;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    public QiTianDaSheng shallowClone(QiTianDaSheng target){
        QiTianDaSheng qiTianDaSheng=new QiTianDaSheng();
        qiTianDaSheng.height=target.height;
        qiTianDaSheng.weight=target.weight;

        qiTianDaSheng.jingubang=target.jingubang;
        qiTianDaSheng.birthday=new Date();
        return qiTianDaSheng;
    }
}

测试代码:

package nju.java.pattern.prototype_pattern;

public class DeepCloneTest {
    public static void main(String[] args) {
        QiTianDaSheng qiTianDaSheng=new QiTianDaSheng();
        try{
            QiTianDaSheng clone=(QiTianDaSheng)qiTianDaSheng.clone();
            System.out.println("深克隆:"+(qiTianDaSheng.jingubang==clone.jingubang));
        }catch (Exception e){
            e.printStackTrace();
        }

        QiTianDaSheng q=new QiTianDaSheng();
        QiTianDaSheng n=q.shallowClone(q);
        System.out.println("潜克隆:"+(q.jingubang==n.jingubang));
    }
}

测试结果如下:

原型模式(Prototype Pattern) 以及clone()方法的源码_第3张图片

4.克隆破坏单例模式

如果我们克隆的目标对象是单例对象,那么意味着深克隆会破坏单例模式。实际上防止克隆破坏单例模式的解决思路非常简单,禁止深克隆即可。要么我们的单例类不实现Cloneable接口,要么我们重写克隆clone()方法,在clone()方法中返回单例对象即可。

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

5.clone()方法的源码

我们常用的ArrayList实现了Cloneable接口,来看clone方法的源码:

 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);
        }
    }

代码位置:https://github.com/YellowDii/MySpring.git

参考书籍:《Spring5核心原理与30个类手写实现》

你可能感兴趣的:(设计模式)