原型模式

定义:

给出一个原型对象实例来指定创建对象的类型,并通过拷贝这些原型的方式来创建新的对象。

原型模式是简单程度仅次于单例模式的简单模式,它的定义可以简单理解为对象的拷贝,通过拷贝的方式创建一个已有对象的新对象,这就是原型模式。

设计类图:

  在原型模式中主要的任务是实现一个接口,这个接口具有一个clone方法可以实现拷贝对象的功能,也就是上图中的ProtoType接口。由于在Java语言中,JDK已经默认给我们提供了一个Coneable接口,所以我们不需要手动去创建ProtoType接口类了。Coneable接口在java中是一个标记接口,它并没有任何方法,只有实现了Coneable接口的类在JVM当中才有可能被拷贝。既然Coneable接口没有任何方法,那clone方法从哪里来呢?由于在java中所有的类都是Object类的子类,所以我们只需要重写来自Object类的clone方法就可以了。
示例代码如下:

public class ProtoTypeClass implements Cloneable {

    @Override
    protected ProtoTypeClass clone() throws CloneNotSupportedException {
        return (ProtoTypeClass)super.clone();
    }
}
public class Client {

    public static void main(String[] args) {
        ProtoTypeClass protoType = new ProtoTypeClass();

        try {
            //通过clone生成一个ProtoTypeClass类型的新对象
            ProtoTypeClass cloneObject = protoType.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

  看吧,原型模式的代码简直不要太简单,简单到不能再简单了,只需要实现Cloneable接口,然后覆写clone方法,调用super.clone就可以实现简单的对象复制了。
  值得注意的是,使用clone方法创建的新对象的构造函数是不会被执行的,也就是说会绕过任何构造函数(有参和无参),因为clone方法的原理是从堆内存中以二进制流的方式进行拷贝,直接分配一块新内存。

深拷贝和浅拷贝

浅拷贝

浅拷贝只会拷贝对象本身相关的基本类型数据,直接看示例代码:

public class EasyCopyExample implements Cloneable {
    private List<String> nameList = new ArrayList<>();

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

    public void addName(String name) {
        nameList.add(name);
    }

    public void printNames() {
        for (String name : nameList) {
            System.out.println(name);
        }
    }
}
public class Client {
    public static void main(String[] args) {
        try {
            //创建一个原始对象并添加一个名字
            EasyCopyExample originalObject = new EasyCopyExample();
            originalObject.addName("test1");

            //克隆一个新对象并添加一个名字
            EasyCopyExample cloneObject = originalObject.clone();
            cloneObject.addName("test2");

            //打印原始对象和新对象的name
            originalObject.printNames();
            System.out.println();
            cloneObject.printNames();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
}

输出结果:
这里写图片描述
  可以看到通过clone()创建的新对象与之前的对象都打印出了相同的name, 这说明新对象与原始对象是共用nameList的这个成员变量的,这就是浅拷贝,拷贝之后的对象会和原始对象共用一部分数据,这样会给使用上带来困扰,因为一个变量不是静态的但却可以多个对象同时修改它的值。在java中除了基本数据类型(int long等)和String类型,数组引用和对象引用的成员变量都不会被拷贝。

深拷贝

为了避免上面的情况,我们就需要对具有clone方法不支持拷贝的数据的对象自行处理,将上述示例代码修改如下:

public class DeepCopyExample implements Serializable {
    private List<String> nameList = new ArrayList<>();
    private String name = "张三";
    private int age = 23;

    public DeepCopyExample 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 (DeepCopyExample) ois.readObject();
    }

    public void addName(String name) {
        nameList.add(name);
    }

    public void printNames() {
        for (String name : nameList) {
            System.out.println(name);
        }
        System.out.println(name);
        System.out.println(age);
    }
}
public class Client {
    public static void main(String[] args) {
        try {
            //创建一个原始对象并添加一个名字
            DeepCopyExample originalObject = new DeepCopyExample();
            originalObject.addName("test1");

            //克隆一个新对象并添加一个名字
            DeepCopyExample cloneObject = originalObject.deepClone();
            cloneObject.addName("test2");

            //打印原始对象和新对象的name
            originalObject.printNames();
            System.out.println("-----------");
            cloneObject.printNames();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果:
这里写图片描述
  上面的代码是通过序列化和反序列化的方式实现对象的拷贝的,通过实现Serializable接口,对象可以写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。可以看到输出结果中新对象保留了原始对象的基本类型数据(name和age),同时针对新对象操作List数据不会影响原始对象,这说明跟原始对象是完全隔离开了,是两个完全独立的对象。

  能够使用这种方式做的前提是,对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。

  有一些对象,比如线程(Thread)对象或Socket对象,是不能简单复制或共享的。不管是使用浅度克隆还是深度克隆,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行创建出相当的同种对象。

原型模式的优缺点

  优点很明显就是可以绕过繁琐的构造函数,快速创建对象,且比直接new一个对象性能优良,因为是直接内存二进制流拷贝。原型模式非常适合于你想要向客户隐藏实例创建的创建过程的场景,提供客户创建未知类型对象的选择。

  原型模式最主要的缺点是每一个类都必须配备一个克隆方法。配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类来说不是很难,而对于已经有的类不一定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。



参考:

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值