前言

本文内容

  • 介绍原型模式
  • 实现原型模式
  • 介绍原型模式的使用场景

    正文

    介绍

    如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。 -设计模式之美

简单的说,就是创建对象后,初始化的属性来自其它对象

实现

实现方式很简单,就是实现 Cloneable , 然后重写下 clone 方法。实现 Cloneable 并没有什么必须要实现的方法,只是用于JVM判断该类可以使用 clone 方法。这种方式直接由 JVM 进行处理,将被拷贝对象的内存中存储的信息复制一份到新内存中,所以不会调用构造器

  1. package cn.zjm404.stu.dp.creat.prototype;
  2. public class ProtoType implements Cloneable{
  3. //属性
  4. //方法
  5. @Override
  6. public ProtoType clone() {
  7. ProtoType res = null;
  8. try{
  9. res = (ProtoType)super.clone();
  10. } catch (CloneNotSupportedException e) {
  11. e.printStackTrace();
  12. }
  13. return res;
  14. }
  15. }

这里涉及到了两种拷贝方式,深拷贝与浅拷贝

浅拷贝与深拷贝

对于类中为引用数据类型的属性,浅拷贝只是把指向被拷贝的数据的地址复制一份进行保存,而深拷贝则是实例化一个同样的引用数据类型的对象,然后将被拷贝的对象的属性复制到新的对象中。
浅拷贝的好处就是拷贝比较快,但是对于还有引用数据类型属性的类,有一部分值是拷贝对象与背靠背对象共享的,如果这些值可以进行修改,那么就会出现问题。就像这样

  1. package cn.zjm404.stu.dp.creat.prototype;
  2. import lombok.*;
  3. @Setter
  4. @Getter
  5. @AllArgsConstructor
  6. @NoArgsConstructor
  7. @ToString
  8. public class MsgObject {
  9. private int id;
  10. private String msg;
  11. }
  1. package cn.zjm404.stu.dp.creat.prototype;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import lombok.ToString;
  5. @Setter
  6. @Getter
  7. @ToString
  8. public class ProtoType implements Cloneable{
  9. private MsgObject msg1;
  10. @Override
  11. public ProtoType clone() {
  12. ProtoType res = null;
  13. try{
  14. res = (ProtoType)super.clone();
  15. } catch (CloneNotSupportedException e) {
  16. e.printStackTrace();
  17. }
  18. return res;
  19. }
  20. }
  1. package cn.zjm404.stu.dp.creat.prototype;
  2. public class Client {
  3. public static void main(String[] args) {
  4. ProtoType protoType = new ProtoType();
  5. protoType.setMsg1(new MsgObject(1,"hello world"));
  6. System.out.println("protoType: " + protoType.toString());
  7. ProtoType protoType1 = protoType.clone();
  8. protoType1.getMsg1().setMsg("HELLO WORLD");
  9. System.out.println("protoType1: " + protoType1.toString());
  10. System.out.println("protoType: " + protoType.toString());
  11. }
  12. }

image.png
可以看到被拷贝对象中的数据被修改了。解决方式有两种,一是对共享数据限制为读,不进行修改。二是使用深拷贝。

使用 Cloneable 来实现深拷贝

保证被拷贝中的引用数据类型属性都实现了 Cloneable ,然后重写被拷贝类的 clone ,将引用属性均使用 clone 方法

  1. package cn.zjm404.stu.dp.creat.prototype;
  2. import lombok.*;
  3. @Setter
  4. @Getter
  5. @AllArgsConstructor
  6. @NoArgsConstructor
  7. @ToString
  8. public class MsgObject implements Cloneable{
  9. private int id;
  10. private String msg;
  11. @Override
  12. public MsgObject clone() throws CloneNotSupportedException {
  13. return (MsgObject) super.clone();
  14. }
  15. }
  1. package cn.zjm404.stu.dp.creat.prototype;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import lombok.ToString;
  5. @Setter
  6. @Getter
  7. @ToString
  8. public class ProtoType implements Cloneable{
  9. private MsgObject msg1;
  10. @Override
  11. public ProtoType clone() throws CloneNotSupportedException {
  12. ProtoType res = (ProtoType) super.clone();
  13. res.msg1 = res.msg1.clone();
  14. return res;
  15. }
  16. }
  1. package cn.zjm404.stu.dp.creat.prototype;
  2. public class Client {
  3. public static void main(String[] args) throws CloneNotSupportedException {
  4. ProtoType protoType = new ProtoType();
  5. protoType.setMsg1(new MsgObject(1,"hello world"));
  6. System.out.println("protoType: " + protoType.toString());
  7. ProtoType protoType1 = protoType.clone();
  8. protoType1.getMsg1().setMsg("HELLO WORLD");
  9. System.out.println("protoType1: " + protoType1.toString());
  10. System.out.println("protoType: " + protoType.toString());
  11. }
  12. }

image.png

使用序列化实现深拷贝

核心方法如下,先将对象中的数据转为 byte 流,然后再进行读取,转为 protoType 返回。

  1. public ProtoType deepClone() throws IOException, ClassNotFoundException {
  2. ByteArrayOutputStream bo = new ByteArrayOutputStream();
  3. ObjectOutputStream oo = new ObjectOutputStream(bo);
  4. oo.writeObject(this);
  5. ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
  6. ObjectInputStream oi = new ObjectInputStream(bi);
  7. return (ProtoType) oi.readObject();
  8. }

完整如下

  1. package cn.zjm404.stu.dp.creat.prototype;
  2. import lombok.Getter;
  3. import lombok.Setter;
  4. import lombok.ToString;
  5. import java.io.*;
  6. @Setter
  7. @Getter
  8. @ToString
  9. public class ProtoType implements Cloneable,Serializable{
  10. private MsgObject msg1;
  11. @Override
  12. public ProtoType clone() throws CloneNotSupportedException {
  13. ProtoType res = (ProtoType) super.clone();
  14. res.msg1 = res.msg1.clone();
  15. return res;
  16. }
  17. public ProtoType deepClone() throws IOException, ClassNotFoundException {
  18. ByteArrayOutputStream bo = new ByteArrayOutputStream();
  19. ObjectOutputStream oo = new ObjectOutputStream(bo);
  20. oo.writeObject(this);
  21. ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
  22. ObjectInputStream oi = new ObjectInputStream(bi);
  23. return (ProtoType) oi.readObject();
  24. }
  25. }
package cn.zjm404.stu.dp.creat.prototype;

import lombok.*;

import java.io.Serializable;

@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class MsgObject implements Cloneable, Serializable {
    private int id;
    private String msg;

    @Override
    public MsgObject clone() throws CloneNotSupportedException {
        return (MsgObject) super.clone();
    }
}
package cn.zjm404.stu.dp.creat.prototype;

import java.io.IOException;

public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ProtoType protoType = new ProtoType();
        protoType.setMsg1(new MsgObject(1,"hello world"));
        System.out.println("protoType: " + protoType.toString());
        ProtoType protoType1 = protoType.deepClone();
        protoType1.getMsg1().setMsg("HELLO WORLD");
        System.out.println("protoType1: " + protoType1.toString());
        System.out.println("protoType: " + protoType.toString());
    }
}

使用场景

如果对象中的数据来自数据库或来自网络时,当再创建新的对象时,可以使用拷贝模式,减少创建时间。

参考