五分钟 掌握 原型模式

开发 前端
大家好,我是老田,今天我给大家分享设计模式中的原型模式。用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式。

 [[404102]]

大家好,我是老田,今天我给大家分享设计模式中的原型模式。用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式。

故事

还记得大四那年找工作,无意中我得从网上找到一份相对漂亮的程序员简历模板,然后全班同学开启疯狂的简历拷贝(U盘)。同时也闹出了一个笑话,有几位同学,拷贝过去的简历,内容完全没改,名字都没有改,截止投给面试官(校招面试官)。后来,结果大家也应该能猜出来,大家都去实习了,部分人还在找工作。后面公司面试官和同伴的其他同学反馈:收到一毛一样的简历,好几份,回来大家一聊就知道问题出哪里了,承认了自己拷贝过去完全没改就拿出去投了,害,尴尬的一匹。

把简历拷贝分为为两种:

  • 一种是拷贝简历,然后把信息修改成自己的
  • 另外一种是,拷贝简历,内容什么都不改。

原型模式定义

Specify the kinds of objects to create using a prototype instance ,and create new objects by coping this prototype

大致意思:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

原型模式:Prototype Pattern,属于创建型模式。

调用者不需要知道任何创建细节,也不用调用构造方法来创建对象。

使用场景

原型模式有如下使用场景:

  • 类初始化消耗资源较多
  • new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
  • 构造函数比较复杂
  • 循环体内生成大量对象时
  • 在Spring中,原型模式应用的非常广泛,例如:scope='prototype'

我们可以将一些getter和setter之类封装成一个工厂方法,然后对于使用的人来说,调用方法就可以了,不需要知道里面的getter和setter是怎么处理的。我们也可以使用JDK提供的实现Cloneable接口,实现快速复制。

创建对象的四种方式:

new、反射、克隆、序列化

实际案例

大家是否有遇到过这种常见,就是项目中规定,不能把与数据库表映射的entity类返回给前端,所以通常返回给前端的有各种O,比如:XxxVO、XxxBO、XxxDTO...

这时候就会出现下面的场景,大家也想已经猜到了。

下面是与数据库表映射的UserEntity实体类。

  1. public class UserEntity { 
  2.     private Long id; 
  3.     private String name
  4.     private Integer age; 
  5.     //....可能还有很多属性 
  6.     //省略getter setter 

返回给前端或者调用方的UserVO实体类。

  1. public class UserVO { 
  2.     private Long id; 
  3.     private String name
  4.     private Integer age; 
  5.     //....可能还有很多属性 
  6.     //省略getter setter 

此时,从数据库里查出来的UserEntity需要转换成UserVO,然后再返回给前端(或者调用方)。

  1. public class ObjectConvertUtil { 
  2.  
  3.     public static UserVo convertUserEntityToUserVO(UserEntity userEntity) { 
  4.         if (userEntity == null) { 
  5.             return null
  6.         } 
  7.         UserVo userVo = new UserVo(); 
  8.  
  9.         userVo.setId(userEntity.getId()); 
  10.         userVo.setName(userEntity.getName()); 
  11.         userVo.setAge(userEntity.getAge()); 
  12.          //如果还有更多属性呢? 
  13.         return userVo; 
  14.     } 

从这个util类中,我们可以看出,如果一个类的属性有几十个,上百个的,这代码量是不是有点恐怖?

于是,我们通常都会使用一些工具类来处理,比如常见有以下:

  1. BeanUtils.copy(); 
  2. JSON.parseObject() 
  3. Guava工具类 
  4. ..... 

这些工具类就用到了原型模式。

通过一个对象,创建一个新的对象。

也把原型模式称之为对象的拷贝、克隆。

其实对象的克隆分浅克隆和深克隆,下面我们就来聊聊浅克隆和深克隆。

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

我们先来聊聊浅克隆,都喜欢由浅入深。

浅克隆

比如,我现在相对用户信息User进行克隆,但是User中有用户地址信息UserAddress属性。

以下是代码的实现:

  1. //用户地址信息 
  2. public class UserAddress  implements Serializable
  3.     private String province; 
  4.     private String cityCode; 
  5.  
  6.     public UserAddress(String province, String cityCode) { 
  7.         this.province = province; 
  8.         this.cityCode = cityCode; 
  9.     } 
  10. //用户信息 
  11. public class User implements Cloneable { 
  12.     private int age; 
  13.     private String name
  14.     //用户地址信息 
  15.     private UserAddress userAddress; 
  16.  
  17.     //getter setter 省略 
  18.  
  19.     @Override 
  20.     protected Object clone() throws CloneNotSupportedException {  
  21.         return super.clone(); 
  22.     } 
  23. //测试 
  24. public class UserTest { 
  25.     public static void main(String[] args) throws Exception { 
  26.         User user = new User(); 
  27.         user.setAge(20); 
  28.         user.setName("田维常"); 
  29.         UserAddress userAddress = new UserAddress("贵州""梵净山"); 
  30.         user.setUserAddress(userAddress); 
  31.  
  32.         User clone = (Useruser.clone(); 
  33.  
  34.         System.out.println("克隆前后UserAddress比较:" + (user.getUserAddress() == clone.getUserAddress())); 
  35.     } 

输出结果

  1. 克隆前后 UserAddress 比较:true 

两个对象属性 UserAddress 指向的是同一个地址。

这就是所谓的浅克隆,只是克隆了对象,对于该对象的非基本类型属性,仍指向原来对象的属性所指向的对象的内存地址。

关系如下:

深克隆

关于深克隆,我们来用一个很经典的案例,西游记里的孙悟空。一个孙悟空能变成n多个孙悟空,手里都会拿着一个金箍棒。

按照前面的浅克隆,结果就是:孙悟空倒是变成很多孙悟空,但是金箍棒用的是同一根。

深克隆的结果是:孙悟空变成了很多个,金箍棒也变成很多个根。

下面我们用代码来实现:

  1. //猴子,有身高体重和生日 
  2. public class Monkey { 
  3.     public int height; 
  4.     public int weight; 
  5.     public Date birthday; 

孙悟空也是猴子,兵器 孙悟空有个金箍棒:

  1. import java.io.Serializable
  2. //孙悟空的金箍棒 
  3. public class JinGuBang implements Serializable
  4.     public float  h=100; 
  5.     public float  d=10; 
  6.     //金箍棒变大 
  7.     public void big(){ 
  8.         this.h *=10; 
  9.         this.d *=10; 
  10.     } 
  11.     //金箍棒变小 
  12.     public void small(){ 
  13.         this.h /=10; 
  14.         this.d /=10; 
  15.     } 

齐天大圣孙悟空:

  1. import java.io.*; 
  2. import java.util.Date
  3.  
  4. //孙悟空有七十二变,拔猴毛生成一个金箍棒 
  5. //使用JDK的克隆机制, 
  6. //实现Cloneable并重写clone方法 
  7. public class QiTianDaSheng extends Monkey implements Cloneable, Serializable { 
  8.  
  9.     public JinGuBang jinGuBang; 
  10.  
  11.     public QiTianDaSheng() { 
  12.         this.birthday = new Date(); 
  13.         this.jinGuBang = new JinGuBang(); 
  14.     } 
  15.  
  16.     @Override 
  17.     protected Object clone() throws CloneNotSupportedException { 
  18.         return this.deepClone(); 
  19.     } 
  20.  
  21.     //深克隆 
  22.     public QiTianDaSheng deepClone() { 
  23.         try { 
  24.             //内存中操作完成、对象读写,是通过字节码直接操作 
  25.             //与序列化操作类似 
  26.             ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
  27.             ObjectOutputStream oos = new ObjectOutputStream(bos); 
  28.             oos.writeObject(this); 
  29.  
  30.             ByteArrayInputStream bais = new ByteArrayInputStream(bos.toByteArray()); 
  31.             ObjectInputStream bis = new ObjectInputStream(bais); 
  32.  
  33.             //完成一个新的对象,底层是使用new创建的一个对象 
  34.             //详情可以了解readObject方法 
  35.             QiTianDaSheng qiTianDaSheng = (QiTianDaSheng) bis.readObject(); 
  36.             //每个猴子的生日不一样,所以每次拷贝的时候,把生日改一下 
  37.             qiTianDaSheng.birthday = new Date(); 
  38.             return qiTianDaSheng; 
  39.         } catch (Exception ex) { 
  40.             ex.printStackTrace(); 
  41.             return null
  42.         } 
  43.     } 
  44.  
  45.     //浅克隆,就是简单的赋值 
  46.     public QiTianDaSheng shalllowClone(QiTianDaSheng target) { 
  47.         QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); 
  48.         qiTianDaSheng.height = target.height; 
  49.         qiTianDaSheng.weight = target.weight; 
  50.  
  51.         qiTianDaSheng.jinGuBang = target.jinGuBang; 
  52.         qiTianDaSheng.birthday = new Date(); 
  53.         return qiTianDaSheng; 
  54.  
  55.     } 

接着我们就来测试一下:

  1. public class DeepCloneTest { 
  2.     public static void main(String[] args) { 
  3.         QiTianDaSheng qiTianDaSheng = new QiTianDaSheng(); 
  4.         try { 
  5.             QiTianDaSheng newObject = (QiTianDaSheng) qiTianDaSheng.clone(); 
  6.             System.out.print("深克隆后 "); 
  7.             System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang)); 
  8.              
  9.         } catch (Exception ex) { 
  10.             ex.printStackTrace(); 
  11.         } 
  12.          
  13.         QiTianDaSheng newObject=qiTianDaSheng.shalllowClone(qiTianDaSheng); 
  14.         System.out.print("浅克隆后 "); 
  15.         System.out.println("金箍棒是否一直:" + (qiTianDaSheng.jinGuBang == newObject.jinGuBang)); 
  16.     } 

输出结果为:

  1. 深克隆后 金箍棒是否一直:false 
  2.  
  3. 浅克隆后 金箍棒是否一直:true 

结论

深克隆后每个孙悟空都有自己的金箍棒,而浅克隆后每个孙悟空用的金箍棒实质上还是同一根。

总结

切记:深和浅,指的是克隆对象里的属性(引用类型)是否指向同一个内存地址。

为了更深刻的理解深克隆和浅克隆,我们回答文中的简历拷贝的故事。

  • 深拷贝:拷贝一份简历,然后对简历中的信息进行修改成自己的
  • 浅拷贝:拷贝一份简历,简历内容完全不变

优点:

  • Java 原型模式基于内存二进制流复制,比直接 new 的性能会更好一些。
  • 可以利用深克隆保存对象状态,存一份旧的(克隆出来),在对其修改,可以充当一个撤销功能。

缺点:

  • 需要配置 clone 方法,改造时需要对已有类进行修改,违背 “开闭原则”。
  • 如果对象间存在多重嵌套引用时,每一层都需要实现克隆。

我们从原型模式的定义,使用场景,真实案例、浅克隆、深克隆、优缺点等方面,对原型模式进行了一个全面的讲解。

一句话总结:

一份简历,全班同学用

本文转载自微信公众号「Java后端技术全栈」,可以通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。

 

责任编辑:武晓燕 来源: Java后端技术全栈
相关推荐

2009-11-17 14:50:50

Oracle调优

2021-01-11 09:33:37

Maven数目项目

2017-01-10 09:07:53

tcpdumpGET请求

2009-11-05 10:55:22

Visual Stud

2018-01-08 16:19:04

微信程序轮播图

2021-01-13 09:23:23

优先队列React二叉堆

2020-10-21 14:29:15

原型模式

2017-04-25 12:07:51

AndroidWebViewjs

2020-06-16 08:47:53

磁盘

2021-10-28 19:09:09

模式原型Java

2021-05-18 08:52:31

Prototype 原型模式设计模式

2021-03-23 15:35:36

Adam优化语言

2020-12-07 11:23:32

Scrapy爬虫Python

2021-10-20 06:58:10

工具低代码无代码

2022-08-04 13:27:35

Pythonopenpyxl

2009-11-16 10:53:30

Oracle Hint

2020-09-11 09:35:18

前端JavaScript策略模式

2009-11-16 09:53:56

PHP上传类

2022-12-16 09:55:50

网络架构OSI

2023-07-23 18:47:59

Docker开源
点赞
收藏

51CTO技术栈公众号