Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46a094e711 | ||
|
|
b5cd1b2379 | ||
|
|
cfe94e6aa7 | ||
|
|
3362f25775 | ||
|
|
87f9704831 | ||
|
|
6c7084dfe1 | ||
|
|
164590159d | ||
|
|
b8ab46d26b | ||
|
|
183b4fc41a |
@@ -179,7 +179,7 @@ const dragProps = {
|
||||
|
||||
```jsx
|
||||
const dropProps = {
|
||||
onDropOver: ev => {
|
||||
onDragOver: ev => {
|
||||
// 做一些样式处理,提示用户此时松手会将元素防止在何处
|
||||
},
|
||||
onDrop: ev => {
|
||||
|
||||
@@ -38,7 +38,7 @@ Builder(生成器)属于创建型模式,针对的是单个复杂对象的
|
||||
|
||||
**意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。**
|
||||
|
||||
我们再理解一次意图,所谓构建与表示分离,就是指一个对象 `Persion` 并不是简单的 `new Persion()` 就可以实例化出来的,如果可以,那就是构建与表示一体。**所谓构建与表示分离,就是指 `Persion` 只能描述,而不能通过 `new Persion()` 实例化,将实例化工作通过 Builder 实现,这样同样一个构建过程可以创建不同的 `Persion` 实例。**
|
||||
我们再理解一次意图,所谓构建与表示分离,就是指一个对象 `Person` 并不是简单的 `new Person()` 就可以实例化出来的,如果可以,那就是构建与表示一体。**所谓构建与表示分离,就是指 `Person` 只能描述,而不能通过 `new Person()` 实例化,将实例化工作通过 Builder 实现,这样同样一个构建过程可以创建不同的 `Person` 实例。**
|
||||
|
||||
在乐高积木的例子中,通过乐高创建的房子并不是 `new House()` 出来,而是将构建与表示分离了,工厂流水线中我们创建一个黄桃罐头,不是通过 `new 黄桃罐头()`,而是通过流水线不同拼装方式来完成,在数据库例子中,我们没有通过 `new DB()` 的方式创建数据库,而是通过 Builder 来创建,这都体现了构建与表示的分离。
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# Facade(外观模式)
|
||||
|
||||
Facade(外观模式)属于结构型模式,是一种日常开发中经常被使用到的设计模式。
|
||||
|
||||
**意图:为子系统中的一组接口提供一个一致的界面,Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。**
|
||||
|
||||
## 举例子
|
||||
|
||||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
|
||||
|
||||
## 意图解释
|
||||
|
||||
### 图书管理员
|
||||
|
||||
图书馆是一个非常复杂的系统,虽然图书按照一定规则摆放,但也只有内部人员比较清楚,作为一位初次来的访客,想要快速找到一本书,最好的办法是直接问图书管理员,而不是先了解这个图书馆的设计,因为你可能要来回在各个楼宇间奔走,借书的流程可能也比较长。
|
||||
|
||||
图书管理员就起到了简化图书馆子系统复杂度的作用,我们只要凡事询问图书管理员即可,而不需要关心他是如何与图书馆内部系统打交道的。
|
||||
|
||||
### 最多跑一次便民服务
|
||||
|
||||
浙江省推出的最多跑一次服务非常方便,很多办事流程都简化了,无论是证件办理还是业务受理,几乎只要跑一次,而必须要持续几天的流程也会通过手机短信或者 App 操作完成后续流程。
|
||||
|
||||
这就相当于外观模式,因为政府系统内部的办事流程可能没有太大变化,但通过抽象出 Facade(外观),让普通市民可以直接与便民办事处连接,而不需要在车管所与驾校之间来回奔波,背后的事情没有少,只是便民办事处帮你做了。
|
||||
|
||||
### Iphone 快捷指令功能
|
||||
|
||||
手机的 App 非常多,而我们需要了解每个功能在哪个 App 上才能运用自如,而快捷指令功能可以将 App 的某些功能单独提取出来,形成一套新的功能组,我们可以只接触到 “拍照” “付款” “计算”,而不用管背后是调用了支付宝还是微信、系统内置摄像机还是其他摄像 App,也不用关心这个 App 内部功能的入口在哪里,这些对接都在快接指令中自动完成。
|
||||
|
||||
快捷指令也是一种外观模式。
|
||||
|
||||
## 意图解释
|
||||
|
||||
**意图:为子系统中的一组接口提供一个一致的界面,Facade 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。**
|
||||
|
||||
为降低一个拥有多个接口的子系统内部复杂性,我们需要一个外观来屏蔽内部的复杂性,因此外观模式就是定义一个高层接口,这个接口直连子系统的内部实现,但调用这个高层接口的人不需要关心子系统内部的实现,这样,对于不想了解子系统内部实现的人来说,提高了易用度。
|
||||
|
||||
当然如果想要深度定制,就可以绕过外观模式,直接使用子系统提供的类,所以说并不是有了外观模式就必须通过外观调用,而是根据实际需要判断使用哪种调用方式。
|
||||
|
||||
## 结构图
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/tfs/TB1j9gZ3.T1gK0jSZFrXXcNCXXa-1082-412.png">
|
||||
|
||||
可以看到,Facade 直接指向子系统中的类,**而子系统的类不会反向指向 Facade**。
|
||||
|
||||
## 代码例子
|
||||
|
||||
下面例子使用 typescript 编写。
|
||||
|
||||
```typescript
|
||||
// 假设一个子系统是三个类结合使用的,为了抽象而解耦开了
|
||||
class A {
|
||||
constructor(b: B) {
|
||||
this.b = b
|
||||
}
|
||||
}
|
||||
|
||||
class B {
|
||||
constructor(c: C) {
|
||||
this.c = c
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
|
||||
}
|
||||
|
||||
// 它们组合成了一种常用功能,我们可以使用外观模式屏蔽子类的细节直接使用
|
||||
class Compile {
|
||||
public run() {
|
||||
const parser = new A(new B(new C))
|
||||
parser.run()
|
||||
}
|
||||
}
|
||||
|
||||
const compile = new Compile()
|
||||
compile.run()
|
||||
```
|
||||
|
||||
这样我们只要知道 `Compile` 类就可以了,而不需要了解背后的 `A` `B` `C` 以及其组合关系。
|
||||
|
||||
## 弊端
|
||||
|
||||
外观模式并不适合于所有场景,当子系统足够易用时,再使用外观模式就是画蛇添足。
|
||||
|
||||
另外,当系统难以抽象出通用功能时,外观模式的设计可能也无所适从,因为设计的高层接口可能适用范围很窄,此时外观模式的意义就比较小。
|
||||
|
||||
## 总结
|
||||
|
||||
其实抽象工厂模式也可以代替外观模式,来实现隐藏子类具体实现的效果,但外观模式描述更具有通用性。
|
||||
|
||||
> 讨论地址是:[精读《设计模式 - Facade 外观模式》· Issue #288 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/288)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,110 @@
|
||||
# Flyweight(享元模式)
|
||||
|
||||
Flyweight(享元模式)属于结构型模式,是一种共享对象的设计模式。
|
||||
|
||||
**意图:运用共享技术有效地支持大量细粒度的对象。**
|
||||
|
||||
## 举例子
|
||||
|
||||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
|
||||
|
||||
### 富文本编辑器的字母对象
|
||||
|
||||
富文本编辑器在英文环境下,其中的文本由大量字母组成,为了便于做统一的格式化、计算等处理,需要将每个字母都存储为对象,但这样存储的代价太大了。
|
||||
|
||||
已知英文字母一共 26 个,所以文档中存在大量重复使用的字母,而每个字母除了位置信息外,其它信息都是相同且只读的,那么有办法降低富文本场景巨大的字母对象数量吗?
|
||||
|
||||
### 网盘存储
|
||||
|
||||
当我们上传一部电影时,有时候几十 GB 的内容不到一秒就上传完了,这是网盘提示你,“已采用极速技术秒传”,你会不会心生疑惑,这么厉害的技术为什么不能每次都生效?
|
||||
|
||||
另外,网盘存储时,同一部电影可能都会存放在不同用户的不同文件夹中,而且电影文件又特别巨大,和富文本类似,电影文件也只有存放位置是不同的,而其余内容都特别巨大且只读,有什么办法能优化存储呢?
|
||||
|
||||
### 大型多人游戏
|
||||
|
||||
玩多人游戏时,为了防止外挂,一般对象的创建与计算是在服务器完成的,那如何保证一个玩家拾取物品后,另一个玩家看到的物品会消失?
|
||||
|
||||
其实道理已经不言而喻了,虽然在不同客户端之间,游戏对象是相互独立的,但在一局游戏中,所有玩家的对象在服务器是共享的。
|
||||
|
||||
## 意图解释
|
||||
|
||||
“共享” 就是享元模式的精髓,将那些大量的,具有很多内部状态而外部状态很少的对象进行共享,就是享元模式的使用方式。
|
||||
|
||||
**意图:运用共享技术有效地支持大量细粒度的对象。**
|
||||
|
||||
共享技术可以理解为缓存,当一个对象创建后,再次访问相同对象时,就不再创建新的对象了,而只有在访问没有被缓存过的对象时,才创建新对象,并立即缓存起来。
|
||||
|
||||
这样做可以有效支持大量细粒度的对象,在富文本例子中,**无数的字母就是大量细粒度对象**,在网盘存储中,**电影文件就是大量细粒度对象**,在大型多人游戏中,**每局游戏内存在大量细粒度对象**。
|
||||
|
||||
这些细粒度对象都拥有相同的特征:
|
||||
|
||||
- 量特别大,这个很容易理解。
|
||||
- 具有大量内部状态,且不随着客户端的不同而改变。
|
||||
- 富文本的字母,不因为展示到不同语句中而发生变化,变化的只有状态;电影文件,不因为放在不同用户的文件夹中而对电影内容产生变化,变化的只有属于哪些用户,放在哪些文件夹里;多人游戏中,同一把武器对象,不因为有多个人的电脑独立运行而拥有更多的弹药,变化的只有在哪些客户端被访问。
|
||||
- 具有少量外部状态,甚至没有外部状态。在上面已经解释了,字母的位置、电影的位置、游戏对象的客户端都是外部状态,这些外部状态相比于其内部状态来说,大小微乎其微,且方便分离存储。
|
||||
|
||||
遇到这种情况,我们就可以将对象内部状态共享,外部状态独立存储,从而节省大量空间。
|
||||
|
||||
尤其是对于网盘的场景,承诺给用户 2 TB 的存储空间,这个用户看到其他人分享了 100 个电影,就点击 “下载到我的网盘”,**此时虽然占用了自己 1 TB 的网盘空间,但实际上网盘运营商并没有增加 1 TB 的存储空间,实际可能增加了 1kb 的存储空间,记录了存储位置**,这就是网盘鸡贼的地方,并不占用空间的内容,却占用了用户真金白银购买的存储空间。
|
||||
|
||||
当然,这就是享元模式的价值,对网盘公司来说,价值巨大,对用户来说,没有价值。所以享元模式的价值体现在全局,比如对整个富文本编辑器来说,减少了巨量字母对象数量,但对于每一个字母对象而言,并没有任何优化。
|
||||
|
||||
## 结构图
|
||||
|
||||
<img width=800 src="https://img.alicdn.com/tfs/TB1KMTY4UY1gK0jSZFMXXaWcVXa-1420-886.png">
|
||||
|
||||
对于 Client 而言,下图描述了如何共享 Flyweight:
|
||||
|
||||
<img width=800 src="https://img.alicdn.com/tfs/TB1JwLL4QL0gK0jSZFtXXXQCXXa-1460-542.png">
|
||||
|
||||
- Flyweight: 共享接口,通过这个接口可以操作对象的外部状态。
|
||||
- ConcreteFlyweight: 实现 Flyweight 接口的对象,这个对象是可被共享的。
|
||||
- UnsharedConcreteFlyweight: 不被共享的对象,因为在享元模式中,实际上并不是所有对象都可以被共享。
|
||||
- FlyweightFactory: 创建并管理 Flyweight 对象,通过其返回的 Flyweight 对象,如果已创建,则会返回之前创建的那个,没有的话才会创建一个新的。
|
||||
- Client: 使用 Flyweight 的客户端。
|
||||
|
||||
通过第二个图可以明显看到,两个不同的 Client 持有了相同 `aConcreteFlyweight` 引用。
|
||||
|
||||
## 代码例子
|
||||
|
||||
下面例子使用 typescript 编写。
|
||||
|
||||
```typescript
|
||||
class FlyweightFactory {
|
||||
public getFlyWeight(key) {
|
||||
if (this.flyweight[key]) {
|
||||
return this.flyweight[key]
|
||||
}
|
||||
|
||||
const flyweight = new Flyweight()
|
||||
this.flyweight[key] = flyweight
|
||||
return flyweight
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`FlyweightFactory` 提供的 `getFlyWeight` 方法,实际上是按照 `key` 对 `flyweight` 实例进行缓存,相同 `key` 下只存储一个 `flyweight` 实例。
|
||||
|
||||
## 弊端
|
||||
|
||||
如果细粒度对象不多,则没必要使用享元模式。
|
||||
|
||||
另外,就算细粒度对象很多,如果对象内部状态并不多,主要都是外部状态,那么享元模式就起不到什么作用了,**因为享元模式通过共享对象,只能节省内部状态,而不能节省外部状态。**
|
||||
|
||||
另外,如果享元模式映射到的共享对象数量并没有比原始对象少出数量级关系,使用的意义也不大。比如富文本编辑器的例子,对于英文来说,一共就 26 个字母,那么 1 万字的文章优化比例是 10000:26,但对于中文文章而言,文字实例本身就很多,可能 1 万字的文章中,汉字去重后依然有 3000 个,那么优化比例就是 10000:3000,此时享元模式的意义就没那么打了。
|
||||
|
||||
## 总结
|
||||
|
||||
享元模式的本质就是尽可能的共享对象,特别适用于存在大量细粒度对象,而这些对象内部状态特别多,外部状态较少的场景。
|
||||
|
||||
对于云存储来说,享元模式是必须使用的,因为云存储的场景决定了,存在大量细粒度文件对象,而存在大量只读的文件,就非常适合共享一个对象,每个用户存储的只是引用。
|
||||
|
||||
> 讨论地址是:[精读《设计模式 - Flyweight 享元模式》· Issue #290 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/290)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
@@ -0,0 +1,100 @@
|
||||
# Proxy(代理模式)
|
||||
|
||||
Proxy(代理模式)属于结构型模式,通过访问代理对象代替访问原始对象,以获得一些设计上的便捷。
|
||||
|
||||
**意图:为其他对象提供一种代理以控制这个对象的访问。**
|
||||
|
||||
## 举例子
|
||||
|
||||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
|
||||
|
||||
### 获得文本对象长度
|
||||
|
||||
获得一个文本对象长度,必须要真正渲染出来,而渲染是比较耗时的,我们可能只在某些场景下需要访问文本对象长度,而更多时候只需要读取文本内容,这两种操作耗时是完全不同的,如何做到业务层调用无感知,来优化执行耗时呢?
|
||||
|
||||
代理模式可以解决这个问题,我们将业务层使用的文本对象替换为代理对象,这个代理对象初始化并不渲染文本,而是在调用文本长度时才渲染。
|
||||
|
||||
### 对象访问保护
|
||||
|
||||
某个大型系统开发完了,突然要求增加代码访问权限体系,不同模块对相同的底层对象拥有不同访问权限,此时这个权限控制逻辑如果写入底层对象,就违背了开闭原则,而对象本身的实现也不再纯粹,增加了维护成本,如何做到不修改对象本身,实现权限控制呢?
|
||||
|
||||
代理模式也能解决,将底层对象导出替换为代理对象,由代理对象控制访问权限即可。
|
||||
|
||||
### 对象与视图双向绑定
|
||||
|
||||
Angular 或 Vue 这类前端框架采用双向绑定视图更新技术,即对象修改后,使用到的视图会自动刷新,这就需要做到以下两点:
|
||||
|
||||
1. 在对象被访问时,记录调用的视图绑定。
|
||||
2. 在对象被修改时,刷新调用它的视图。
|
||||
|
||||
问题是,在业务代码使用对象与修改对象的地方插入这段逻辑,显然会增加巨大的维护成本,如何做到业务层无感知呢?
|
||||
|
||||
代理模式可以很好的解决这个问题,其实业务层拿到的对象已经是代理对象了,它在被访问与被修改时,都会执行固定的钩子做视图绑定与视图刷新。
|
||||
|
||||
## 意图解释
|
||||
|
||||
**意图:为其他对象提供一种代理以控制这个对象的访问。**
|
||||
|
||||
代理模式的意图很容易理解,就是通过代理对象代替原始对象的访问。
|
||||
|
||||
这只是代理模式的实现方式,代理模式真正的难点不在于理解它是如何工作的,而是理解哪些场景适合用代理,或者说创建了代理对象,怎么用才能发挥它的价值。
|
||||
|
||||
在上面例子中,已经举出了几种常见代理使用场景:
|
||||
|
||||
1. 对开销大的对象使用代理,以按需使用。
|
||||
2. 对需要保护的对象进行代理,在代理层做权限控制。
|
||||
3. 在对象访问与修改时要执行一些其他逻辑,适合在代理层做。
|
||||
|
||||
## 结构图
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/imgextra/i3/O1CN01eZHGHQ28t0oeHYzas_!!6000000007989-2-tps-1262-522.png">
|
||||
|
||||
使用时关系如下:
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/imgextra/i4/O1CN01iwyMKQ1KbOnR0N2AP_!!6000000001182-2-tps-1270-206.png">
|
||||
|
||||
Subject 定义的是 RealSubject 与 Proxy 共用的接口,这样任何使用 RealSubject 的地方都可以使用 Proxy。
|
||||
|
||||
RealSubject 指的是原始对象,Proxy 是一个代理实体。
|
||||
|
||||
关系图中可以看出,当客户端要访问 subject 时,第一层访问的是 Proxy 代理,由这个代理将 realSubject 转发给客户端。
|
||||
|
||||
## 代码例子
|
||||
|
||||
下面例子使用 typescript 编写。
|
||||
|
||||
```typescript
|
||||
// 对象 obj
|
||||
const proxy = new Proxy(obj, {
|
||||
get(target,key) {}
|
||||
set(target,key,value) {}
|
||||
})
|
||||
```
|
||||
|
||||
JS 创建代理还是蛮简单的,代理可以控制对象的所有成员属性,包括成员变量与成员方法的访问(get)与修改(set)。
|
||||
|
||||
## 弊端
|
||||
|
||||
代理模式会增加微弱的开销,因此请不要将所有对象都变成代理,没有意义的代理只会徒增程序开销。
|
||||
|
||||
另外代理对象过多,也会导致调试困难,因为代理层的存在,我们往往可能忽略这一层带来的影响,导致忘记这个对象其实是一个代理。
|
||||
|
||||
## 总结
|
||||
|
||||
代理和继承有足够多的相似之处,继承中,子类几乎可以人为是对父类的代理,子类可以重写父类的方法。但代理和继承还是有区别的:
|
||||
|
||||
如果你没有采用 `new Proxy` 这种 API 创建代理,而是采用继承的方式实现,你会一下子继承这个类的所有方法,而做不到按需控制访问权限的灵活效果,所以代理比继承更加灵活。
|
||||
|
||||
JS 的 `new Proxy` 对应了 Java 动态代理模式,一般认为动态代理比静态代理更强大。
|
||||
|
||||
最后,还要重申那句话,代理模式理解与运用并不难,难就难在能否在恰当的场合想到它,双向绑定几乎是代理模式最好的例子。
|
||||
|
||||
> 讨论地址是:[精读《设计模式 - Proxy 代理模式》· Issue #291 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/291)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
|
||||
|
||||
> 关注 **前端精读微信公众号**
|
||||
|
||||
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
|
||||
|
||||
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
|
||||
Reference in New Issue
Block a user