![](http://www.eleven-smile.com/usr/uploads/2022/04/411798795.png)

一、定义

原型模式(Prototype Pattern)是指原型实例指定创建并且通过拷贝这些原型创建新的对象。调用者不需要知道任何创建细节,不调用构造函数。原型模式属于创建型模式。

二、应用场景

1、类初始化消耗资源过多 2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等) 3、构造函数比较复杂 4、循环体重生产大量对象时,如:Spring中 scope="prototype"

三、原型模式的常见写法

1、通用写法

public interface IPrototype<T> {
    T clone();
}
public class ConCreatePrototype implements IPrototype{
    private int age;
    private String name;

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

    @Override
    public ConCreatePrototype clone() {
        ConCreatePrototype conCreatePrototype = new ConCreatePrototype();
        conCreatePrototype.setAge(this.age);
        conCreatePrototype.setName(this.name);
        return conCreatePrototype;
    }

    @Override
    public String toString() {
        return "ConCreatePrototype{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

测试代码:

public class ConTest {
    public static void main(String[] args) {
        //创建原型对象
        ConCreatePrototype prototype = new ConCreatePrototype();
        prototype.setAge(18);
        prototype.setName("eleven-smile");
        System.out.println(prototype);
        //拷贝原型对象
        ConCreatePrototype clonePrototype = prototype.clone();
        System.out.println(clonePrototype);

    }
}

测试结果:

ConCreatePrototype{age=18, name='eleven-smile'}
ConCreatePrototype{age=18, name='eleven-smile'}

2、浅克隆

JDK层面已经帮我们实现了一个线程API,我们只需要实现Cloneable接口即可.

public class Prototype implements Cloneable{

    private String name;

    private int age;

    private List<String> hobbies = new ArrayList<>();

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

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void addHobby(String hobby){
        hobbies.add(hobby);
    }

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

    public Prototype() {
        super();
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobbies=" + hobbies +
                '}';
    }
}

测试代码:

public static void main(String[] args) {
        Prototype prototypeA = new Prototype();
        prototypeA.setName("张三");
        prototypeA.setAge(18);
        prototypeA.addHobby("学习");
        System.out.println(prototypeA);

        System.out.println("---------浅克隆--------");

        try {
            Prototype prototypeB = (Prototype) prototypeA.clone();
            prototypeB.setName("李四");
            prototypeB.setAge(20);
            prototypeB.addHobby("玩");
            System.out.println(prototypeB);
            //再次打印prototypeA
            System.out.println(prototypeA);
            System.out.println("prototypeA:"+prototypeA.getHobbies()+",prototypeB:"+prototypeB.getHobbies());
            System.out.println(prototypeA.getHobbies()==prototypeB.getHobbies());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

测试结果:

Prototype{name='张三', age=18, hobbies=[学习]}
---------浅克隆--------
Prototype{name='李四', age=20, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习, 玩]}
prototypeA:[学习, 玩],prototypeB:[学习, 玩]
true

分析: 原型对象是 张三 18 学习(引用类型) 克隆对象是 李四 20 玩(引用类型) 结果却变成了学习 玩 同时 张三也是学习 玩 这就是浅克隆带来的问题,原型对象和克隆对象共用同一个引用类型,都指向该引用类型的地址。

3、深克隆

3.1 硬编码

public class Prototype implements Cloneable{

    private String name;

    private int age;

    private ArrayList<String> hobbies = new ArrayList<>();

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

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void addHobby(String hobby){
        hobbies.add(hobby);
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Prototype prototype = (Prototype) super.clone();
        //对引用类型hobbies 也做clone
        prototype.hobbies = (ArrayList<String>) this.hobbies.clone();
        return prototype;
    }

    public Prototype() {
        super();
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobbies=" + hobbies +
                '}';
    }
}

测试代码:

public static void main(String[] args) {
        Prototype prototypeA = new Prototype();
        prototypeA.setName("张三");
        prototypeA.setAge(18);
        prototypeA.addHobby("学习");
        System.out.println(prototypeA);

        System.out.println("---------深克隆--------");

        try {
            //调用clone方法,根据原型A 克隆 B
            Prototype prototypeB = (Prototype) prototypeA.clone();
            prototypeB.setName("李四");
            prototypeB.setAge(20);
            prototypeB.addHobby("玩");
            System.out.println(prototypeB);

            System.out.println(prototypeA);
            //发现了这里就是浅克隆和深克隆的差异点,深克隆相当于重新new了一个hobbies的ArrayList对象 即 每当根据原型A
            //克隆了 B 甚至 C、D 等时 就会new 一个属于克隆对象的hobbies的ArrayList对象
            System.out.println("prototypeA:" + prototypeA.getHobbies() + ",prototypeB:"
                    + prototypeB.getHobbies());
            System.out.println(prototypeA.getHobbies() == prototypeB.getHobbies());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }

测试结果:

Prototype{name='张三', age=18, hobbies=[学习]}
---------深克隆--------
Prototype{name='李四', age=20, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习]}
prototypeA:[学习],prototypeB:[学习, 玩]
false

分析: 造成这样的结果差异,因为这是深克隆,之前的是浅克隆,在深克隆的Prototype类中的clone方法中对引用类型List<String>也做了clone(这里就是用了ArrayList.clone)。深克隆相当于重新new了一个hobbies的ArrayList对象 即 每当根据原型A 克隆了 B 甚至 C、D 等时 就会new 一个属于克隆对象的hobbies的ArrayList对象

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

这就是ArrayList.clone源码,其实只是将List的元素循环遍历了一遍,这种就是硬编码,试想一下,如果在对象重声明了各种几何类型,那每种情况都需要单独处理,那岂不是很麻烦。

3.2 序列化

对上述浅克隆代码进行改良。

public class Prototype implements Cloneable, Serializable {

    private String name;

    private int age;

    private List<String> hobbies;

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

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

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public List<String> getHobbies() {
        return hobbies;
    }


    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    //序列化 深克隆
    public Prototype deepClone() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Prototype)ois.readObject();
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", hobbies=" + hobbies +
                '}';
    }
}

测试代码:

public static void main(String[] args) {
        Prototype prototypeA = new Prototype();
        prototypeA.setName("张三");
        prototypeA.setAge(18);
        List<String> hobbies = new ArrayList<>();
        hobbies.add("学习");
        prototypeA.setHobbies(hobbies);
        System.out.println(prototypeA);

        System.out.println("---------序列化深克隆--------");

        try {
            Prototype prototypeB = (Prototype) prototypeA.deepClone();
            prototypeB.getHobbies().add("玩");
            System.out.println(prototypeB);
            //再次打印prototypeA
            System.out.println(prototypeA);
            System.out.println("prototypeA:"+prototypeA.getHobbies()+",prototypeB:"+prototypeB.getHobbies());
            System.out.println(prototypeA.getHobbies()==prototypeB.getHobbies());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试结果:

Prototype{name='张三', age=18, hobbies=[学习]}
---------序列化深克隆--------
Prototype{name='张三', age=18, hobbies=[学习, 玩]}
Prototype{name='张三', age=18, hobbies=[学习]}
prototypeA:[学习],prototypeB:[学习, 玩]
false

四、克隆破坏单例模式

思考:假如我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。 实际上防止克隆破坏单例的解决思路非常简单,禁止深克隆即可。

1、单例类不实现Cloneable接口

public class Prototype implements Serializable{

}

2、重写clone()方法,在clone方法中返回单例对象即可。

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

五、原型模式的优缺点

优点:

1、性能优良,Java自带的。原型模式是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。 2、可以使用深克隆方法保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候(例如回复到历史某一状态),可辅助实现撤销操作。

缺点:

1、需要为每一个类配置一个克隆方法。 2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代,违反了开闭原则。 3、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦,因此,深拷贝、浅拷贝都需要运用得当。