惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
TaoSecurity Blog
TaoSecurity Blog
Cloudbric
Cloudbric
Cisco Talos Blog
Cisco Talos Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
H
Heimdal Security Blog
The Last Watchdog
The Last Watchdog
T
Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
Security Latest
Security Latest
Know Your Adversary
Know Your Adversary
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Securelist
Microsoft Azure Blog
Microsoft Azure Blog
The GitHub Blog
The GitHub Blog
阮一峰的网络日志
阮一峰的网络日志
D
Docker
V
Vulnerabilities – Threatpost
Attack and Defense Labs
Attack and Defense Labs
Hugging Face - Blog
Hugging Face - Blog
W
WeLiveSecurity
Engineering at Meta
Engineering at Meta
aimingoo的专栏
aimingoo的专栏
Last Week in AI
Last Week in AI
L
LINUX DO - 热门话题
NISL@THU
NISL@THU
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Cloudflare Blog
博客园_首页
P
Privacy International News Feed
Scott Helme
Scott Helme
N
News and Events Feed by Topic
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
T
Tenable Blog
H
Hacker News: Front Page
N
News and Events Feed by Topic
罗磊的独立博客
Google Online Security Blog
Google Online Security Blog
S
Security @ Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
A
About on SuperTechFans
有赞技术团队
有赞技术团队
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
G
GRAHAM CLULEY
Application and Cybersecurity Blog
Application and Cybersecurity Blog

Java技术经验分享

生产故障处理SOP分享 | Java技术经验分享 系统稳定性建设实践总结【转载】 | Java技术经验分享 valine访问leancloud国际版异常,评论失效修复 | Java技术经验分享 匆匆忙忙的2021 | Java技术经验分享 聊一下换工作 | Java技术经验分享 IoT系列(2):WIFI设备常见配网方案介绍 | Java技术经验分享 IoT系列(1):什么是物联网 | Java技术经验分享 Java8特性2 - StreamApi | Java技术经验分享 Java8特性1 - lambda表达式&函数式接口 | Java技术经验分享 设计模式(5)-适配器模式 | Java技术经验分享 设计模式(4)-建造者模式 | Java技术经验分享 设计模式(2)-工厂模式图文介绍 | Java技术经验分享 设计模式(1)-带你了解3类8种单例模式 | Java技术经验分享 Java时间处理5---Java8中时区相关类库介绍 | Java技术经验分享 Java时间处理4---Java8中LocalDate、LocalTime、LocalDateTime介绍 | Java技术经验分享 Java时间处理3---Java8中Instant、Duration、Period、Clock介绍 | Java技术经验分享 一些有意思的问答 | Java技术经验分享 Nacos系列博客说明 | Java技术经验分享 菜鸡程序员的2019年度总结 | Java技术经验分享 Java中“附近的人”实现方案讨论及代码实现 | Java技术经验分享 Java时间处理2----时区TimeZone类方法探究(Java8以前) | Java技术经验分享 Java时间处理1----Date和Calendar方法探究(Java8以前) | Java技术经验分享 FastJson中JSONString、JavaBean、JSONObject、JSONArray的转换关系及API示例 | Java技术经验分享 2019.11软考软件设计师归来心得体会及复习备考指南 | Java技术经验分享 你还没用过“约定式提交”吗?那你赶紧来补补知识吧 | Java技术经验分享 教你如何看懂UML中的类图及类图中的关系 | Java技术经验分享 设计模式总览 | Java技术经验分享 萌新入门Github请看这里,学不会远程教 | Java技术经验分享 Hexo的工作原理探究 | Java技术经验分享 Hexo-theme-butterfly修改调整记录教程 | Java技术经验分享 排序8:基数排序 | Java技术经验分享 排序7:归并排序 | Java技术经验分享 排序6:快速排序 | Java技术经验分享 排序5:冒泡排序 | Java技术经验分享 排序4:堆排序 | Java技术经验分享 排序3:选择排序 | Java技术经验分享 排序2:希尔排序 | Java技术经验分享 排序1:直接插入排序 | Java技术经验分享 推荐一款博客一文多发的良心工具OpenWrite | Java技术经验分享 近期学习计划 | Java技术经验分享 Nacos(九):Nacos集群部署和遇到的问题 | Java技术经验分享 Nacos(八):Nacos持久化 | Java技术经验分享 Nacos(七):Nacos共享配置 | Java技术经验分享 Nacos(六):多环境下如何“管理”及“隔离”配置和服务 | Java技术经验分享 Nacos(五):多环境下如何“读取”Nacos中相应的配置 | Java技术经验分享 Nacos(四):SpringCloud项目中接入Nacos作为配置中心 | Java技术经验分享 Nacos(三):Nacos与OpenFeign的对接使用 | Java技术经验分享 Nacos(二):SpringCloud项目中接入Nacos作为注册中心 | Java技术经验分享 Nacos(一):Nacos介绍 | Java技术经验分享 20190719小组分享 | Java技术经验分享 Java中equals和HashCode方法的分析 | Java技术经验分享 Java中==和equals方法的分析 | Java技术经验分享 Java中的自动拆装箱、装箱缓存 | Java技术经验分享 About-blog | Java技术经验分享 Java中的编译、反编译和反编译工具全家桶分享 | Java技术经验分享 finalize()的生命周期(执行过程) | Java技术经验分享 Java关键字之final、finally与finalize方法 | Java技术经验分享 Java中重写、重载 | Java技术经验分享 Java中面向对象的三大特征:继承、封装、多态 | Java技术经验分享 DockerFile介绍 | Java技术经验分享 Docker环境下安装Gitlab | Java技术经验分享 Docker中私有仓库的搭建流程 | Java技术经验分享 Centos7下两种方式安装Docker-CE | Java技术经验分享 Vert.x创建一个Http服务 | Java技术经验分享 Vert.x创建TCP服务端及客户端 | Java技术经验分享 Vert.x Core(二)- Event Bus(事件总线) | Java技术经验分享 Vert.x-Core(一)- 基础篇 | Java技术经验分享 SpringBoot项目中实现国际化 | Java技术经验分享 Vert.x介绍 | Java技术经验分享 毕设选题项目本地运行环境搭建教程 | Java技术经验分享 Jupyter Notebooks的安装和使用介绍 | Java技术经验分享 算法笔试题:1元,5元,10元,20元,50元、100元面值人民币组合给定x元的问题 | Java技术经验分享 Quartz学习总结 | Java技术经验分享 SpringBoot2.x集成Redis | Java技术经验分享 SpringBoot2.x集成MongoDB | Java技术经验分享 [SpringCloud学习] - 浅谈微服务架构 | Java技术经验分享 基于hexo和coding免费搭建个人博客网站 | Java技术经验分享 Hello World | Java技术经验分享
设计模式(3)-原型模式与浅拷贝和深拷贝 | Java技术经验分享
文章作者: LarsCheng · 2020-09-05 · via Java技术经验分享

概念

在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,
用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。

原型模式定义:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象

模拟需求

现在有一辆车,他的名字叫做哈啰单车,它的价格是2元/1小时,请编写程序创建多辆哈啰单车

简单分析后涉及到以下几个类:

  • 车辆类 Vehicle.java
  • 测试类 Client.java

传统方式

先来看下最容易理解的方式:

1
2
3
4
5
6
7
public class Vehicle {

private String name;
private double price;


}
1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle("哈啰单车", 2.0);
Vehicle vehicle1 = new Vehicle(vehicle.getName(),vehicle.getPrice());
Vehicle vehicle2 = new Vehicle(vehicle.getName(),vehicle.getPrice());
System.out.println(vehicle.hashCode());
System.out.println(vehicle1.hashCode());
System.out.println(vehicle2.hashCode());
}
}

执行测试类代码可以看到创建了另外3个属性相同但引用完全不同的哈啰单车

执行结果如下:

1
2
3
1836019240
325040804
1173230247

原型模式

上面的对象复制方式是比较容易理解的,但是如果要复制很多对象时,每次都要get/set ,工作量必然很大

那有没有其他的复制方式吗?当然有了,设计模式中有一种原型模式的设计理念

原型模式的概念我们上文也有提到:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象

原型模式是一种创建型设计模式,创建方无需了解创建的细节,原型模式所涉及到的角色和类图如下

  • 抽象原型类Prototype:抽象原型类,声明一个克隆自己的方法
  • 具体的原型实现类ConcretePrototype:具体的原型类,实现克隆自己的方法
  • 客户类Client:客户调用方,克隆对象

原型模式

原型模式示例

对于模式场景中,要求复制多个不同的对象的需求,使用原型模式则有了新的解决方案如下

Java中Object类提供一个clone方法。该方法可以将一个Java对象复制一份。
如果某一个要使用clone方法,必须先实现Cloneable接口,Cloneable接口表示该类能够复制并且具有复制能力

涉及到的类和角色如下:

  • 抽象原型类:Cloneable接口,声明了clone方法
  • 具体原型类:Vehicle类,有自己的对象属性,并且实现了clone方法
  • Client:调用测试

只需要将上面的代码加以修改即可:

1
2
3
4
5
6
7
8
9
10
11
12
public class Vehicle implements Cloneable {

private String name;
private double price;

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


}
1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Vehicle vehicle = new Vehicle("哈啰单车", 2.0);

Vehicle vehicle1 = (Vehicle) vehicle.clone();
Vehicle vehicle2 = (Vehicle) vehicle.clone();
System.out.println(vehicle.hashCode());
System.out.println(vehicle1.hashCode());
System.out.println(vehicle2.hashCode());
}
}

依旧执行测试方法,来看看通过clone方法是否复制出了不同的对象

执行结果:

1
2
3
1836019240
325040804
1173230247

通过上面的一个小场景,对原型模式进行了简单的演示。但是上面的原型模式在一些特殊情况下可能就会出现问题

模拟需求2

现在有一车辆类,他有名称、单价、所属公司三个属性;所属公司对象包含了公司名称属性,请编写程序创建多辆美团单车,单价为2元/小时,所属公司为美团点评

简单分析后涉及到以下几个类:

  • 车辆类 Vehicle.java
  • 公司类 Company.java
  • 测试类 Client.java

在上文原型模式的代码中加上Company类的代码和Vehicle类的公司属性后如下

1
2
3
4
5
public class Company {
private String name;


}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Vehicle implements Cloneable {

private String name;
private double price;
private Company company;

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


}

Client代码如下

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) throws CloneNotSupportedException {
Vehicle vehicle = new Vehicle("美团单车", 2.0, new Company("美团点评"));

Vehicle vehicle1 = (Vehicle) vehicle.clone();
Vehicle vehicle2 = (Vehicle) vehicle.clone();
System.out.println("vehicle.hashCode(): "+vehicle.hashCode()+" company.hashCode(): "+ vehicle.getCompany().hashCode());
System.out.println("vehicle1.hashCode(): "+vehicle1.hashCode()+" company.hashCode(): "+ vehicle1.getCompany().hashCode());
System.out.println("vehicle2.hashCode(): "+vehicle2.hashCode()+" company.hashCode(): "+ vehicle2.getCompany().hashCode());
}
}

观察克隆对象后的输出结果,你就会发现问题所在

1
2
3
vehicle.hashCode(): 1836019240 company.hashCode(): 325040804
vehicle1.hashCode(): 1173230247 company.hashCode(): 325040804
vehicle2.hashCode(): 856419764 company.hashCode(): 325040804

三个车辆对象的hashCode都不相同,说明有被成功克隆,但是其中的公司属性(对象类型)的hashCode并没有被同步克隆,内容中只有一份Company对象

相当于这次的克隆,内存中创建了三个不同车辆(Vehicle)对象,但是公司(Company)对象只有一个,被三个车辆对象所引用。

理论上,在创建了第一个车辆对象后,连续克隆两次后,内存中应该有3个车辆对象和3个公司对象。

这里就出现了原型模式中会存在的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 浅拷贝&深拷贝

关于浅拷贝的描述:

- 数据类型为基本类型的成员变量,在调用默认clone方法后,会进行浅拷贝,即将该属性的值复制一份给新的对象
- 数据类型为引用类型的成员变量,比如一个数组、一个对象,在调用默认的clone方法后,只会将成员变量的引用地址指向新的对象,而不会克隆新的成员变量对象

这种现象即为`浅拷贝`,上面的几个例子严格来说都属于浅拷贝,因为都没有去考虑成员变量为引用类型时的对象克隆

`深拷贝`自然是解决了浅拷贝的缺陷,对整个对象进行完全深度的对象复制,包括对象的引用类型和基本类型成员变量

# 深拷贝应用

针对模拟需求2,使用深拷贝的方式进行代码实现

实现思路:Company和Vehicle都实现Cloneable接口,重写Vehicle的clone方法

```java
public class Company implements Cloneable {
private String name;

@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
/**getter&setter&toString.......*/
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Vehicle implements Cloneable {

private String name;
private double price;
private Company company;

@Override
protected Object clone() throws CloneNotSupportedException {
Vehicle vehicle = (Vehicle) super.clone();
vehicle.setCompany((Company) this.company.clone());
return vehicle;
}


}

Client代码无需变动,执行Client进行测试,结果如下:

1
2
3
vehicle.hashCode(): 1836019240 company.hashCode(): 325040804
vehicle1.hashCode(): 1173230247 company.hashCode(): 856419764
vehicle2.hashCode(): 621009875 company.hashCode(): 1265094477

可以看到三个对象的对象成员属性明显都是不同的,说明做到了深拷贝

常见的原型模式的运用

Spring中配置bean的时候,scope属性可以配置一个prototype值,该值指定该bean的创建是使用原型模式

1
2
//示例:
<bean id="userDaoImpl" scope="prototype" class="com.larscheng.www.dao.impl.UserDaoImpl"/>

当通过getBean方法获取bean时,可以看到源码中对于scope属性进行了处理

结语

  • 当我们要创建新的对象过于复杂时,可以考虑使用原型模式来进行创建
  • 使用原型模式时,需要考虑到浅拷贝和深拷贝