YAZONG 我的开源

设计模式(六)创建型模式(对象创建型模式)原型模式-PROTOTYPE

 
0 评论0 浏览

2、6 原型模式

定义

1)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2)这种不通过 new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
3)原型模式先产生一个包含大量共用信息的类,然后拷贝出副本,修正细节信息,建立了一个完整的个性对象。
4)一个对象的产生可以不由零起步,直接从一个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。
5)原型模式其实就是从一个对象再创建另外一个可定制的对象,而不需要知道任何创建的细节。
6)一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高(相对每次 new 来说)。
7)不用初始化对象,而是动态地获得对象运行时的状态。
8)当创建给定类的实例的过程很昂贵或很复杂时,就使用原型模式。
9)原型模式允许你通过复制现有的实例来创建新的实例(在 Java 中,这通常意味着使用 clone()方法,或者反序列化)。这个模式的重点在于,客户的代码在不知道要实例化何种特定类的情况下,可以制造出新的实例。

通用类图

image.png

原型模式的核心是一个 clone 方法,通过该方法进行对象的拷贝,Java 提供了一个 Cloneable 接口来标识这个对象是可拷贝的。这个接口只是一个标记作用,这个 JVM 中具有这个标记的对象才有可能被拷贝。覆盖/重写 clone()方法才能从”有可能被拷贝”转换为”可以被拷贝”。

通用源码

public class Client1 {
}
class PrototypeClass implements Cloneable{

    @Override
    protected Object clone() throws CloneNotSupportedException {

        PrototypeClass prototypeClass = null;
        try {
            prototypeClass = (PrototypeClass)super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return prototypeClass;
    }
}

优点缺点

1)性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2)逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要在实际应用时考虑。
3)向客户隐藏制造新实例的复杂性。
4)提供让客户能够产生未知类型对象的选项。
5)在某些环境下,复制对象比创建新对象更有效。

使用原型模式的缺点:对象的复制有时相当复杂。

适用场景

1)资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
2)性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
3)一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且多个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
4)线程不安全也可以通过对象的复制功能来解决这个问题。
5)在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时,可以考虑原型。

在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。

注意事项

构造函数不会被执行

案例

public class Client2 {
    public static void main(String[] args) {
        //产生一个对象
        System.out.println("1111111111111111");
        Thing thing = new Thing();
        System.out.println("2222222222222222");
        //拷贝一个对象
        System.out.println("3333333333333333");
        Thing cloneThing = thing.clone();
        System.out.println("4444444444444444");
    }
}
class Thing implements Cloneable{

    public Thing() {
        System.out.println("构造函数被执行");
    }

    @Override
    public Thing clone() {

        Thing thing = null;
        try {
            thing = (Thing)super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return thing;
    }
}


1111111111111111
构造函数被执行
2222222222222222
3333333333333333
4444444444444444

描述

对象拷贝时构造函数确实没有被执行,因为 Object 类的 clone 方法的原理是从内存中(具体来说是从堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,所以构造函数没有被执行也是非常正常的了。

浅拷贝

案例

import java.util.ArrayList;
import java.util.List;

public class Client3 {
    public static void main(String[] args) {
        //产生一个对象
        Thing thing = new Thing();
        thing.setValue("zhangsan");
        thing.setNum1(11);
        thing.setNum2(12);
        CloneModel cloneModel1 = new CloneModel();
        cloneModel1.setStrtest("strtestval1");
        thing.setCloneModel(cloneModel1);
        thing.setStr1("str11val");
        //拷贝一个对象
        Thing cloneThing1 = thing.clone();
        cloneThing1.setValue("lisi.wangwu.");
        cloneThing1.setNum1(21);
        cloneThing1.setNum2(22);
        CloneModel cloneModel2 = new CloneModel();
        cloneModel2.setStrtest("strtestval2");
        cloneThing1.setCloneModel(cloneModel2);
        cloneThing1.setStr1("str12val");

        System.out.println("----------clone-before-----------");

        System.out.println(thing.getValue());
        System.out.println(thing.getNum1());
        System.out.println(thing.getNum2());
        System.out.println(thing.getStr1());
        System.out.println(thing.getCloneModel().getStrtest());

        System.out.println("----------clone-after-----------");

        System.out.println(cloneThing1.getValue());
        System.out.println(cloneThing1.getNum1());
        System.out.println(cloneThing1.getNum2());
        System.out.println(cloneThing1.getStr1());
        System.out.println(cloneThing1.getCloneModel().getStrtest());


    }
}
class Thing implements Cloneable{

    private List<String> arrayList = new ArrayList<>();
    private CloneModel cloneModel = new CloneModel();
    private String str1 = "str1val";
    private int num1 = 1;
    private Integer num2 = 1;

    @Override
    public Thing clone() {

        Thing thing = null;
        try {
            thing = (Thing)super.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return thing;
    }

    public void setValue(String value){
        this.arrayList.add(value);
    }

    public List<String> getValue(){
        return this.arrayList;
    }

    public void setStr1(String str){
        this.str1 = str;
    }

    public String getStr1(){
        return this.str1;
    }

    public void setNum1(int num){
        this.num1 = num;
    }

    public int getNum1(){
        return this.num1;
    }

    public void setNum2(int num){
        this.num2 = num;
    }

    public int getNum2(){
        return this.num2;
    }

    public CloneModel getCloneModel() {
        return cloneModel;
    }

    public void setCloneModel(CloneModel cloneModel) {
        this.cloneModel = cloneModel;
    }
}
class CloneModel{

    private String strtest;

    public String getStrtest() {
        return strtest;
    }

    public void setStrtest(String strtest) {
        this.strtest = strtest;
    }

}


----------clone-before-----------
[zhangsan, lisi.wangwu.]
11
12
str11val
strtestval1
----------clone-after-----------
[zhangsan, lisi.wangwu.]
21
22
str12val
strtestval2

描述

可以发现案例中输出了 lisi.wangwu。因为 Object 类提供的方法 clone 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。(一种非常不安全的方式)

注意:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:
一是类的成员变量,而不是方法内变量;
二是必须是一个可变的引用对象,而不是一个原始类型(比如 int、long、char 等)或不可变对象(String 是没有 clone 方法的,处理机制也比较特殊,通过字符串池 stringpool 在需要的时候才在内存中创建新的字符串。)

深拷贝

案例

import java.util.ArrayList;
import java.util.List;

public class Client4 {
    public static void main(String[] args) {
        //产生一个对象
        Thing thing = new Thing();
        thing.setValue("zhangsan");
        //拷贝一个对象
        Thing cloneThing = thing.clone();
        cloneThing.setValue("lisi.wangwu.");

        System.out.println(thing.getValue());

    }
}
class Thing implements Cloneable{

    //这里要用ArrayList而不是List(要指明具体的类型)
    //这里不要加final(要使用clone方法,类的成员变量上不要增加final关键字,用了final怎能重新赋值?)
    private ArrayList<String> arrayList = new ArrayList<>();

    @Override
    public Thing clone() {

        Thing thing = null;
        try {
            thing = (Thing)super.clone();
            //这里一定要加上thing
            //注意不会被拷贝的条件:可变的引用对象不会被拷贝。
            //因为个人理解上为这个实体的clone和集合的clone本质并不是一体的,虽然在同一个方法体中且在方法内来处理。
            thing.arrayList = (ArrayList<String>) this.arrayList.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
        return thing;
    }

    public void setValue(String value){
        this.arrayList.add(value);
    }

    public List<String> getValue(){
        return this.arrayList;
    }

}

[zhangsan]

描述

该方法实现了完全的拷贝,两个对象之间没有任何的瓜葛。深拷贝还有一种实现方式就是通过写二进制流来操作对象,然后实现深拷贝。


标题:设计模式(六)创建型模式(对象创建型模式)原型模式-PROTOTYPE
作者:yazong
地址:https://blog.llyweb.com/articles/2020/06/04/1591281200167.html