【原型模式】

原型模式 Prototype Pattern

  • 属于创建型模式
  • 是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,调用者不需要知道任何创建细节,不调用构造函数
  • 关键点:不通过 new 关键字,而是通过方法去创建对象

原型模式的使用场景

  • 类初始化消耗资源较多
  • new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体中生产大量对象

通过硬编码复制对象

编写比较麻烦

java 复制代码
@Data
public class User {
    
    private Long id;
    private String name;
    private String pwd;
    private String addr;
    private String mobile;
    private int age;
    // ... 属性非常多 ...
    
    /**
     * 硬编码复制
     */
    public User copy() {
        User user = new User();
        user.setId(this.getId());
        user.setName(this.getName());
        user.setPwd(this.getPwd());
        user.setAddr(this.getAddr());
        user.setMobile(this.getMobile());
        user.setAge(this.getAge());
        // ...这里还有很多属性set...
        return user;
    }
    
}

通过反射复制对象

本质也是 set、get

java 复制代码
public static Object copy(Object prototype) {
    Class<?> clazz = prototype.getClass();
    Object returnValue;
    try {
        returnValue = clazz.newInstance();
        // 获取所有属性, 并设置可访问
        for (Field field : clazz.getDeclaredFields()) {
            field.setAccessible(true);
            field.set(returnValue, field.get(prototype));
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return returnValue;
}

浅拷贝复制对象

浅拷贝的特点

  • 对于基本类型是按值复制,不会因为原对象的基本类型值的改变而影响到拷贝后的对象
  • 对于引用类型,prototype 和 clone 对象引用同一个引用类型对象(复制引用对象的内存地址)

浅拷贝存在的问题

  • 修改 clone[prototype] 引用的对象后,会影响到 prototype[clone]
java 复制代码
@Data
public class ConcretePrototype implements Cloneable {
    
    private String name;
    private int age;
    private List<String> hobbies;
    
    /**
     * 实现 Cloneable 接口, 底层使用字节码进行克隆
     */
    @Override
    public ConcretePrototype clone() {
        try {
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }
}

测试及结果

修改 prototype 的 hobbies 后,clone 的 hobbies 也被修改了

java 复制代码
public static void main(String[] args) {
    ShallowConcretePrototype prototype = new ShallowConcretePrototype();
    prototype.setAge(20);
    prototype.setName("yes");
    ArrayList<String> hobbies = new ArrayList<>();
    hobbies.add("书法");
    hobbies.add("象棋");
    prototype.setHobbies(hobbies);
    ShallowConcretePrototype clone = prototype.clone();
    
    System.out.println("prototype == clone : " + (prototype == clone));
    
    prototype.setAge(25);
    System.out.println("==== 修改基本类型 age ====");
    System.out.println(prototype);
    System.out.println(clone);
    
    prototype.hobbies.add("游戏");
    
    System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
    System.out.println("==== 修改引用类型 hobbies ====");
    
    System.out.println(prototype.hobbies);
    System.out.println(clone.hobbies);
    
    System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
}

流实现深拷贝复制对象

java 复制代码
@Data
public class DeepConcretePrototype implements Serializable
, Cloneable {
    
    private String name;
    private int age;
    private List<String> hobbies;
    
    @Override
    public DeepConcretePrototype clone(){
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (DeepConcretePrototype)ois.readObject();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

测试及结果

java 复制代码
public static void main(String[] args) {
    DeepConcretePrototype prototype = new DeepConcretePrototype();
    prototype.setAge(20);
    prototype.setName("yes");
    ArrayList<String> hobbies = new ArrayList<>();
    hobbies.add("书法");
    hobbies.add("象棋");
    prototype.setHobbies(hobbies);
    DeepConcretePrototype clone = prototype.clone();
    
    System.out.println("prototype == clone : " + (prototype == clone));
    
    prototype.setAge(25);
    System.out.println("==== 修改基本类型 age ====");
    System.out.println(prototype);
    System.out.println(clone);
    
    prototype.hobbies.add("游戏");
    
    System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
    System.out.println("==== 修改引用类型 hobbies ====");
    
    System.out.println(prototype.hobbies);
    System.out.println(clone.hobbies);
    
    System.out.println("prototype.hobbies == clone.hobbies : " + (prototype.hobbies == clone.hobbies));
}

json 字符串实现深拷贝复制对象

todo

防止单例被原型模式破坏

1、单例类不实现 cloneable 接口

2、实现 cloneable,但是 clone 方法直接返回单例对象

原型模式在源码中的应用

ArrayList

HashMap

总结

优点:

  • 性能优良,Java 自带的原型模式是基于二进制流的拷贝,比直接 new 一个对象性能上提升了许多
  • 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建过程

缺点:

  • 必须匹配克隆方法
  • 当对已有类进行改造的时候,需要修改代码,违反了开闭原则
  • 深拷贝、浅拷贝需要运用得当

via

https://www.bilibili.com/video/BV1nZ4y1i7tn?p=6

相关推荐
morning_judger18 小时前
【设计模式系列】原型模式(十一)
java·设计模式·原型模式
飞升不如收破烂~2 天前
在Spring框架中,容器管理的bean可以有不同的作用域(scope),其中最常用的两种是单例(singleton)和原型(prototype)。
spring·单例模式·原型模式
安泽13142 天前
【修订中】js 中apply call bind 用法
原型模式
Komorebi_99993 天前
JavaScript 判断数据类型有哪些方法?
开发语言·javascript·原型模式
无敌岩雀4 天前
C++设计模式创建型模式———原型模式
c++·设计模式·原型模式
一条晒干的咸魚4 天前
【Web前端】JavaScript 对象原型与继承机制
开发语言·前端·javascript·原型模式·web前端
shinelord明4 天前
【再谈设计模式】原型模式~复制的魔法师
开发语言·设计模式·原型模式
wrx繁星点点6 天前
解释器模式:有效处理语言的设计模式
java·开发语言·spring·servlet·设计模式·解释器模式·原型模式
wrx繁星点点8 天前
原型模式:高效的对象克隆解决方案
数据结构·spring·spring cloud·java-ee·maven·intellij-idea·原型模式
csdn小瓯9 天前
前端八股文第一篇
前端·原型模式