设计模式-原型模式(Prototype)
设计模式-原型模式(Prototype)
概要
记忆关键词:原型实例、拷贝
定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
分析:原型模式就是从一个样板对象中复制出一个内部属性一致的对象。它是在内存中拷贝二进制流,比new一个对象的性能好很多。
原型模式结构图如下:
一、深拷贝和浅拷贝
要学习原型模式,我们首先要了解一下深拷贝和浅拷贝的概念。
Java中的数据类型,分为基本类型和引用类型。在一个方法里的变量如果是基本类型的话,变量就直接存储在这个方法的栈帧里,例如int、long等;而引用类型则在栈帧里存储这个变量的指针,指向堆中该实体的地址,例如String、Array等。深拷贝和浅拷贝是只针对引用数据类型的。
比如一个方法有一个基本类型参数和一个引用类型参数,在方法体里对参数重新赋值,会影响传入的引用类型参数,而不会影响基本类型参数,因为基本类型参数是值传递,而引用类型参数是引用传递。
这其中也包含着例外,比如String类型和大小不超过127的Long类型,虽然也是引用类型,却像基本类型一样不受影响。这是因为它们会先比较常量池维护的值。
1. 浅拷贝
浅拷贝是在按位(bit)拷贝对象,这个对象有着原始对象属性值的一份精确拷贝。
如果有嵌套对象的时候,使用浅拷贝会带来数据安全方面的隐患,这就到了需要深拷贝的时候。
2. 深拷贝
对于有多层对象的,每个对象都需要实现Cloneable接口并重写clone()方法,才可以实现了对象的串行层层拷贝。
说明:彻底的深拷贝几乎是不可能实现的,那样不但可能存在引用关系非常复杂的情况,也可能存在引用链的某一级上引用了一个没有实现Cloneable接口的第三方对象的情况。
二、应用场景
1. 原型模式有一个最典型的应用,是在Java的Object类当中。
在Java语言中,Object类实现了Cloneable接口,一个对象可以通过调用clone()方法生成对象,这就是原型模式的典型应用。
但需要注意的是,clone()方法并不是Cloneable接口里的,而是Object类里的,Cloneable是一个标识接口,标识这个类的对象是可被拷贝的,如果没有实现Cloneable接口,却调用了clone()方法,就会报错。
Object#clone():
Cloneable接口:
让我们针对new和clone的操作进行一下对比:
代码示例:
User类
1 @AllArgsConstructor 2 @NoArgsConstructor 3 @Getter 4 @Setter 5 public class User implements Cloneable{ 6 private String name; 7 private int age; 8 9 @Override 10 public String toString() { 11 return super.toString(); 12 } 13 14 @Override 15 protected User clone() throws CloneNotSupportedException { 16 try{ 17 User newUser = (User)super.clone(); 18 newUser.setName(this.name); 19 return newUser; 20 }catch (CloneNotSupportedException e) { 21 e.printStackTrace(); 22 } 23 return null; 24 } 25 }
测试类Client:
1 public class Client { 2 private final User user = new User("lily", 20); 3 4 public void testNew() { 5 6 User user1 = new User("小灰", 18); 7 8 } 9 10 public void testClone() throws CloneNotSupportedException { 11 12 User user2 = user.clone(); 13 14 } 15 }
通过jclasslib工具查看bytecode,可以看出二者对栈资源的消耗:
可以看到clone的栈资源消耗更少,因此性能比new更好一些。
这里还要注意一点:类需要实现 Cloneable 接口,并重写 clone 方法。在 clone 方法中,要确保对引用类型进行适当的克隆,以防止浅拷贝问题。
2. 使用XBeanCopier进行属性复制
虽然可以通过实现Cloneable接口和覆盖clone方法来实现原型模式,但这种方式有一些局限性和复杂性。这里将介绍如何使用XBeanCopier作为一个高效的 JavaBean 属性拷贝工具,是如何进行属性复制的。
BeanCopier 类属于 Hutool 工具包 中的 cn.hutool.core.bean.copier 包。
Hutool 是一个非常轻量级的 Java 工具库,它提供了丰富的工具类来简化常见的开发任务。XBeanCopier是其中一个用于对象属性复制的工具类,提供了灵活的拷贝选项,包括字段映射、忽略空值、深度复制等功能。
1)copyProperties 方法
XBeanCopier 通过cglib动态代理操作字节码,生成一个复制类,触发点为BeanCopier.create
代码示例:
1 /** 2 * 对象拷贝 3 * @param source 源对象 4 * @param target 目标对象 5 */ 6 public static void copyProperties(Object source, Object target) { 7 if (Objects.isNull(source)){ 8 return; 9 } 10 BeanCopier copier = getBeanCopier(source.getClass(), target.getClass()); 11 copier.copy(source, target, null); 12 } 13 14 /** 15 * 对象拷贝(自动生成目标对象) 16 * @param source 源对象 17 * @param targetClass 目标类型class文件 18 * @param <T> 目标泛型 19 * @return 20 */ 21 public static <T> T copyProperties(Object source, Class<T> targetClass) { 22 if(Objects.isNull(source)){ 23 return null; 24 } 25 T t = getConstructorAccess(targetClass).newInstance(); 26 copyProperties(source, t); 27 return t; 28 }
2)copyPropertiesOfList方法
1 /** 2 * List对象拷贝(自动生成目标对象) 3 * @param sourceList 源对象List 4 * @param targetClass 目标类型class文件 5 * @param <T> 目标泛型 6 * @return 7 */ 8 public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) { 9 if (sourceList.isEmpty()) { 10 return Collections.emptyList(); 11 } 12 ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass); 13 List<T> resultList = new ArrayList<>(sourceList.size()); 14 for (Object o : sourceList) { 15 T t; 16 try { 17 t = constructorAccess.newInstance(); 18 copyProperties(o, t); 19 resultList.add(t); 20 } catch (Exception e) { 21 throw new RuntimeException(e); 22 } 23 } 24 return resultList; 25 }
XBeanCopier 是一个高效的 JavaBean 属性拷贝工具,它基于字节码操作实现了对象属性的快速拷贝。XBeanCopier 的工作原理如下:
- 属性拷贝:XBeanCopier 通过字节码操作直接访问源对象和目标对象的属性,并将源对象的属性值赋值给目标对象对应的属性。
- 无构造函数调用:由于 XBeanCopier 是通过字节码操作进行属性拷贝的,而不是通过创建新对象,因此不会调用目标对象的构造函数。
- 性能优化:通过字节码操作进行属性拷贝,避免了反射带来的性能开销。
三、核心用途
1. 解决构建复杂对象的资源消耗问题,提升创建对象的效率。(比如在循环体内产生大量对象的时候)
2. 保护性拷贝,防止外部对只读对象进行修改。
四、注意事项
原型模式中的拷贝不会执行构造函数,这是因为原型模式是通过克隆来创建新对象的,而不是通过构造函数。
当我们使用原型模式时,需要注意一些情况:
1) 成员变量的克隆
如果对象中包含引用类型的成员变量,需要确保这些引用类型也能正确地进行克隆。如果不进行深拷贝,那么原型模式的克隆对象和原对象中的引用类型成员将指向相同的对象,这可能导致意外的修改。
2)构造函数中的初始化工作
由于克隆不会执行构造函数,因此如果在构造函数中进行了一些重要的初始化工作,我们需要确保这些初始化工作在克隆后得到正确的结果。
有时候,我们可能需要在克隆方法中手动执行必要的初始化步骤。
五、总结
绝大多数设计模式都是牺牲性能提升开发效率的,原型模式则是为数不多的牺牲开发效率提升性能的设计模式。
参考链接:https://mp.weixin.qq.com/s/fOIbjrouBCM9CLgEf_m2mA