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

那何为“对象的创建成本比较大”?
实际上,创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。

但是,如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作

这么说还是比较理论,接下来,我们通过一个例子来解释一下刚刚这段话。

假设数据库中存储了大约 10 万条“搜索关键词”信息,每条信息包含关键词、关键词被搜索的次数、信息最近被更新的时间等。系统 A 在启动的时候会加载这份数据到内存中,用于处理某些其他的业务需求。为了方便快速地查找某个关键词对应的信息,我们给关键词建立一个散列表索引。

如果你熟悉的是 Java 语言,可以直接使用语言中提供的 HashMap 容器来实现。其中,HashMap 的 key 为搜索关键词,value 为关键词详细信息(比如搜索次数)。我们只需要将数据从数据库中读取出来,放入 HashMap 就可以了。

不过,我们还有另外一个系统 B,专门用来分析搜索日志,定期(比如间隔 10 分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对 v2 版本的数据进行更新,得到 v3 版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为。


为了保证系统 A 中数据的实时性(不一定非常实时,但数据也不能太旧),系统 A 需要定期根据数据库中的数据,更新内存中的索引和数据。

我们该如何实现这个需求呢?实际上,也不难。

我们只需要在系统 A 中,记录当前数据的版本 Va 对应的更新时间 Ta,从数据库中捞出更新时间大于 Ta 的所有搜索关键词,也就是找出 Va 版本与最新版本数据的“差集”,然后针对差集中的每个关键词进行处理。如果它已经在散列表中存在了,我们就更新相应的搜索次数、更新时间等信息;如果它在散列表中不存在,我们就将它插入到散列表中。

按照这个设计思路,我给出的示例代码如下所示


public class Demo {private ConcurrentHashMap<String, SearchWord> currentKeywords = new ConcurrentHashMap<>();private long lastUpdateTime = -1;public void refresh() {// 从数据库中取出更新时间>lastUpdateTime的数据,放入到currentKeywords中List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);long maxNewUpdatedTime = lastUpdateTime;for (SearchWord searchWord : toBeUpdatedSearchWords) {if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {maxNewUpdatedTime = searchWord.getLastUpdateTime();}if (currentKeywords.containsKey(searchWord.getKeyword())) {currentKeywords.replace(searchWord.getKeyword(), searchWord);} else {currentKeywords.put(searchWord.getKeyword(), searchWord);}}lastUpdateTime = maxNewUpdatedTime;}private List<SearchWord> getSearchWords(long lastUpdateTime) {// TODO: 从数据库中取出更新时间>lastUpdateTime的数据return null;}
}

不过,现在,我们有一个特殊的要求:任何时刻,系统 A 中的所有数据都必须是同一个版本的,要么都是版本 a,要么都是版本 b,不能有的是版本 a,有的是版本 b。那刚刚的更新方式就不能满足这个要求了。除此之外,我们还要求:在更新内存数据的时候,系统 A 不能处于不可用状态,也就是不能停机更新数据。

那我们该如何实现现在这个需求呢?实际上,也不难。我们把正在使用的数据的版本定义为“服务版本”,当我们要更新内存中的数据的时候,我们并不是直接在服务版本(假设是版本 a 数据)上更新,而是重新创建另一个版本数据(假设是版本 b 数据),等新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b。这样既保证了数据一直可用,又避免了中间状态的存在。

按照这个设计思路,我给出的示例代码如下所示:


public class Demo {private HashMap<String, SearchWord> currentKeywords=new HashMap<>();public void refresh() {HashMap<String, SearchWord> newKeywords = new LinkedHashMap<>();// 从数据库中取出所有的数据,放入到newKeywords中List<SearchWord> toBeUpdatedSearchWords = getSearchWords();for (SearchWord searchWord : toBeUpdatedSearchWords) {newKeywords.put(searchWord.getKeyword(), searchWord);}currentKeywords = newKeywords;}private List<SearchWord> getSearchWords() {// TODO: 从数据库中取出所有的数据return null;}
}

不过,在上面的代码实现中,newKeywords 构建的成本比较高。我们需要将这 10 万条数据从数据库中读出,然后计算哈希值,构建 newKeywords。这个过程显然是比较耗时。为了提高效率,原型模式就派上用场了。

我们拷贝 currentKeywords 数据到 newKeywords 中,然后从数据库中只捞出新增或者有更新的关键词,更新到 newKeywords 中。而相对于 10 万条数据来说,每次新增或者更新的关键词个数是比较少的,所以,这种策略大大提高了数据更新的效率。

按照这个设计思路,我给出的示例代码如下所示:


public class Demo {private HashMap<String, SearchWord> currentKeywords=new HashMap<>();private long lastUpdateTime = -1;public void refresh() {// 原型模式就这么简单,拷贝已有对象的数据,更新少量差值HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);long maxNewUpdatedTime = lastUpdateTime;for (SearchWord searchWord : toBeUpdatedSearchWords) {if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {maxNewUpdatedTime = searchWord.getLastUpdateTime();}if (newKeywords.containsKey(searchWord.getKeyword())) {SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());oldSearchWord.setCount(searchWord.getCount());oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());} else {newKeywords.put(searchWord.getKeyword(), searchWord);}}lastUpdateTime = maxNewUpdatedTime;currentKeywords = newKeywords;}private List<SearchWord> getSearchWords(long lastUpdateTime) {// TODO: 从数据库中取出更新时间>lastUpdateTime的数据return null;}
}

这里我们利用了 Java 中的 clone() 语法来复制一个对象。如果你熟悉的语言没有这个语法,那把数据从 currentKeywords 中一个个取出来,然后再重新计算哈希值,放入到 newKeywords 中也是可以接受的。毕竟,最耗时的还是从数据库中取数据的操作。相对于数据库的 IO 操作来说,内存操作和 CPU 计算的耗时都是可以忽略的。不过,不知道你有没有发现,实际上,刚刚的代码实现是有问题的。

要弄明白到底有什么问题,我们需要先了解另外两个概念:深拷贝(Deep Copy)和浅拷贝(Shallow Copy)。

总结

  • 原型模式 就是通过拷贝创建对象

思考

  • 为何要用原型模式?
  • 什么时候需要使用原型模式?
  • 什么是深拷贝?什么是浅拷贝?

参考

47 | 原型模式:如何最快速地clone一个HashMap散列表?

原型模式的原理与应用相关推荐

  1. 设计模式之原型模式(Prototype)

    原文地址:http://www.cnblogs.com/BeyondAnyTime/archive/2012/05/19/2508963.html 1.初识原型模式 大家都知道连锁机构是现在灰常流行的 ...

  2. 第 7 章 原型模式

    第 7 章 原型模式 1.克隆羊问题 克隆羊问题描述 现在有一只羊tom, 姓名为: tom,年龄为: 1, 颜色为:白色,请编写程序创建和tom羊属性完全相同的10只羊 传统模式解决克隆羊问题 类图 ...

  3. (二十三)原型模式详解(clone方法源码的简单剖析)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 原型模式算是JAVA中最简单 ...

  4. 设计模式---原型模式(Prototype Pattern)

    在编程中有时候我们会发现,当我们需要一个实例,可是这个实例的创建过程十分复杂,在执行过程中 会消耗大量的时间,同时创建第一个实例和创建第二个时间的初始化信息并未改变.在此种情况下,直接New 一个实例 ...

  5. Java设计模式(工厂模式>抽象工厂模式和原型模式)

    Java设计模式Ⅱ 1.工厂模式 1.1 简单工厂模式 1.2 工厂方法模式 2.抽象工厂模式 3.总结 4.原型模式 4.1 原型模式 4.2 浅拷贝 4.3 深拷贝 5.建造者模式 1.工厂模式 ...

  6. golang设计模式之原型模式

    原型模式 wiki:原型模式是创建型模式的一种,其特点在于通过"复制"一个已经存在的实例来返回新的实例,而不是新建实例.被复制的实例就是我们所称的"原型",这个 ...

  7. 【设计模式】原型模式 ( 概念简介 | 使用场景 | 优缺点 | 基本用法 )

    文章目录 I . 原型模式 概念简介 II . 原型模式 使用场景 III . 原型模式 优缺点 IV . 原型模式 实现及 简单示例 I . 原型模式 概念简介 原型模式 : 用原型实例指定创建对象 ...

  8. 原型模式(ProtoType) - Java里的对象复制

    一, 引用的复制和对象复制. 在编程中, 我们有时会用两个引用指向同一个对象. 例如: ArrayList a = new ArrayLIst(); ArrayList b = a; 看起来好像有a, ...

  9. 原型模式的应用场景_23中设计模式(上)

    学习主题:设计模式 学习目标: 对应视频: http://www.itbaizhan.cn/course/id/85.html 对应文档: 无 对应作业 1. 单例模式_应用场景_饿汉式_懒汉式(视频 ...

最新文章

  1. 时差法超声波流量计的原理
  2. pycharm 怎么对代码进行性能分析,消耗时间time (Run -- Profile 'xxxx')
  3. 计算机系学生的简单立体宿舍装潢大赏
  4. python中的类装饰器应用场景_Python 自定义装饰器使用写法及示例代码
  5. vscode git使用_vscode中使用git
  6. STSdb,最强纯C#开源NoSQL和虚拟文件系统
  7. smarty模板引擎(一)基础知识
  8. python三级联动菜单_2分钟制作智能式联动下拉菜单,轻松搞定重复内容,录入不出错...
  9. Linux开发_多线程编程
  10. 如何从 iCloud 共享文件和文件夹?
  11. 今天 Java 14 正式发布了!放弃 Java 8 行吗?
  12. C语言 库函数:qsort 详解
  13. 用matlab画相频曲线_用MATLAB进行系统频率特性曲线绘制
  14. DELMIA软件:机器人固定点焊仿真
  15. Canvas Api(全)
  16. ubuntu 安装 TM2009 QQ2013
  17. 一文详解ERP的提取原理
  18. 揭秘青岛富二代接班路线 曝红领集团小美女总裁(图)-青青岛社区
  19. 为什么Uber微服务架构使用多租户?
  20. 微信小程序-点击按钮退出小程序

热门文章

  1. 深度学习自学(三十二):半监督焦点人物检测
  2. win10企业版打开自带截图工具
  3. mysql除了两列其他都选_从MySQL的两列中选择不同的名称,然后在单列中显示结果...
  4. 基础06final、权限、内部类
  5. Postman工具(环境变量与全局变量)
  6. silverlight安装后网页_纯干货收藏|两个技巧教你完美长截屏网页!
  7. 用python画熊猫代码_python-使用Pandas绘制包含列表的列
  8. 计算机组成原理在线实验,《计算机组成原理》实验.doc
  9. python 接口设计_手把手教你在机器学习过程设计Python接口
  10. java中table属性_div实现table功能