feature: design patter

This commit is contained in:
binpeng.liu
2023-10-31 15:17:07 +08:00
parent 4b8c957341
commit 32eda66409
29 changed files with 3809 additions and 16 deletions

View File

@@ -0,0 +1,37 @@
# 原型模式
对于创建型模式,之前的文章已经讲了单例模式、工厂模式、建造者模式,今天我们来讲最后一个:原型模式。
对于熟悉 JavaScript 语言的前端程序员来说,原型模式是一种比较常用的开发模式。这是因为,有别于 Java、C++ 等基于类的面向对象编程语言JavaScript 是一种基于原型的面向对象编程语言。即便 JavaScript 现在也引入了类的概念,但它也只是基于原型的语法糖而已。不过,如果你熟悉的是 Java、C++ 等这些编程语言,那在实际的开发中,就很少用到原型模式了
## 原型模式的原理与应用
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式 Prototype Design Pattern简称原型模式。
## 何为“对象的创建成本比较大”?
创建对象包含的申请内存、给成员变量赋值这一过程,本身并不会花费太多时间,或者说对于大部分业务系统来说,这点时间完全是可以忽略的。应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。
但是,如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取,这种情况下,我们就可以利用原型模式,从其他已有对象中直接拷贝得到,而不用每次在创建新对象的时候,都重复执行这些耗时的操作。
举个例子:
假设数据库中存储了大约 10 万条“搜索关键词”信息,每条信息包含关键词、关键词被搜索的次数、信息最近被更新的时间等。系统 A 在启动的时候会加载这份数据到内存中,用于处理某些其他的业务需求。为了方便快速地查找某个关键词对应的信息,我们给关键词建立一个散列表索引。
不过,我们还有另外一个系统 B专门用来分析搜索日志定期比如间隔 10 分钟)批量地更新数据库中的数据,并且标记为新的数据版本。比如,在下面的示例图中,我们对 v2 版本的数据进行更新,得到 v3 版本的数据。这里我们假设只有更新和新添关键词,没有删除关键词的行为
为了保证系统 A 中数据的实时性(不一定非常实时,但数据也不能太旧),系统 A 需要定期根据数据库中的数据,更新内存中的索引和数据。
要求,任何时刻,系统 A 的所有数据都是一个版本的,要么都是版本 a要么都是版本 b不能有的是版本 a有的是版本 b。那刚刚的更新方式就不能满足这个要求了。除此之外我们还要求在更新内存数据的时候系统 A不能处于不可用状态也就是不能停机更新数据
方案:我们把正在使用的数据的版本定义为“服务版本”,当我们要更新内存中的数据的时候,我们并不是直接在服务版本(假设是版本 a 数据)上更新,而是重新创建另一个版本数据(假设是版本 b 数据),等新的版本数据建好之后,再一次性地将服务版本从版本 a 切换到版本 b。这样既保证了数据一直可用又避免了中间状态的存在。
可以利用语言提供的 Java 的 clone 或者 OC 的 copy 来实现复制一个对象。但存在深拷贝和浅拷贝2个概念。
## 原型模式的实现方式:深拷贝和浅拷贝
浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象......而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
那如何实现深拷贝呢?总结一下的话,有下面两种方法。
第一种方法:递归拷贝对象、对象的引用对象以及引用对象的引用对象......直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止
第二种方法:先将对象序列化,然后再反序列化成新的对象。
风险:如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了