Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f783678da | ||
|
|
299a60a881 |
@@ -0,0 +1,140 @@
|
||||
# Observer(观察者模式)
|
||||
|
||||
Observer(观察者模式)属于行为型模式。
|
||||
|
||||
**意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。**
|
||||
|
||||
拿项目的 npm 依赖举例子:npm 包与项目是一对多的关系(一个 npm 包被多个项目使用),当 npm 包发布新版本时,如果所有依赖于它的项目都能得到通知,并自动更新这个包的版本号,那么就解决了包版本更新的问题,这就是观察者模式要解决的基本问题。
|
||||
|
||||
## 举例子
|
||||
|
||||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
|
||||
|
||||
### 对象与视图双向绑定
|
||||
|
||||
在 [精读《设计模式 - Proxy 代理模式》](https://github.com/dt-fe/weekly/blob/v2/178.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%20-%20Proxy%20%E4%BB%A3%E7%90%86%E6%A8%A1%E5%BC%8F%E3%80%8B.md) 中我们也提到了双向绑定概念,只不过代理是实现双向绑定的一个具体方案,而观察者模式才是在描述双向绑定这个概念。
|
||||
|
||||
观察者模式在最初提出的时候,就举了数据与 UI 相互绑定的例子。即同一份数据可以同时渲染为表格与柱状图,那么当操作表格更新数据时,如何让柱状图的数据也刷新?从这个场景引出了对观察者模式的定义,即 “数据” 与 “UI” 是一对多的关系,我们需要一种设计模式实现当 “数据” 变化时,所有依赖于它的 “UI” 都得到通知并自动更新。
|
||||
|
||||
### 拍卖
|
||||
|
||||
拍卖由一个拍卖员与多为拍卖者组成。拍卖时,由 A 同学喊出的竞价(我出 100)就是观察者向目标发出的 `setState` 同时,此时拍卖员喊出(有人出价 100,还有更高的吗?)就是一个 `notify` 通知行为,拍卖员通知了现场竞价全员,刷新了他们对当前最高价的信息。
|
||||
|
||||
### 聊天室
|
||||
|
||||
聊天室由一个中央服务器与多个客户端组成。客户端发送消息后,就是向中央服务器发送了 `setState` 更新请求,此时中央服务器通知所有处于同一聊天室的客户端,更新他们的信息,从而完成一次消息的发送。
|
||||
|
||||
## 意图解释
|
||||
|
||||
数据与 UI 的例子已经详细说明了其意图含义,这里就不赘述了。
|
||||
|
||||
## 结构图
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/imgextra/i4/O1CN011HxE9E24luDnEQiqA_!!6000000007432-2-tps-1774-670.png">
|
||||
|
||||
- Subject: 目标,即例子中的 “数据”。
|
||||
- Observer: 观察者,即例子中的 “表格”、“柱状图”。
|
||||
|
||||
还是以数据与 UI 同步为例,当表格发生操作修改数据时,表格这个 TableObserver 会调用 Subject(数据)的 `setState`,此时数据被更新了。然后数据这个 `Subject` 维护了所有监听(包括表格 `TableObserver` 与柱状图 `ColumnChartObserver`),此时 `setState` 内会调用 `notify` 遍历所有监听,并依次调用 `Update` 方法,每个监听的 `Update` 方法都会调用 `getState` 获取最新数据,从而实现表格更新后 -> 更新数据 -> 表格、柱状图同时刷新。
|
||||
|
||||
为了更好的理解,以这张协作图为例:
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/imgextra/i1/O1CN01QuF29i1RpKAEcCPrX_!!6000000002160-2-tps-1578-728.png">
|
||||
|
||||
- `aConcreteSubject`: 对应例子中的数据。
|
||||
- `aConcreteObserver`: 对应例子中的表格。
|
||||
- `anotherConcreteObserver`: 对应例子中的柱状图。
|
||||
|
||||
## 代码例子
|
||||
|
||||
下面例子使用 typescript 编写。
|
||||
|
||||
> PS: 为了简化处理,就不定义 Subject 接口与 ConcreteSubject 了,而是直接用 Subject 类代替。Observer 也同理。
|
||||
|
||||
```typescript
|
||||
// 目标,管理所有观察者
|
||||
class Subject {
|
||||
// 观察者数组
|
||||
private observers: Observer[] = []
|
||||
// 状态
|
||||
private state: State
|
||||
|
||||
// 通知所有观察者
|
||||
private notify() {
|
||||
this.observers.forEach(eachObserver => {
|
||||
eachObserver.update()
|
||||
})
|
||||
}
|
||||
|
||||
// 新增观察者
|
||||
public addObserver(observer: Observer) {
|
||||
this.observers.push(observer)
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
public setState(state: State) {
|
||||
this.state = state
|
||||
this.notify()
|
||||
}
|
||||
|
||||
// 读取状态
|
||||
public getState() {
|
||||
return this.state
|
||||
}
|
||||
}
|
||||
|
||||
// 观察者
|
||||
class Observer {
|
||||
// 维护目标
|
||||
private subject: Subject
|
||||
|
||||
constructor(subject: Subject) {
|
||||
this.subject = subject
|
||||
this.subject.addObserver(this)
|
||||
}
|
||||
|
||||
// 更新
|
||||
public update() {
|
||||
// 比如渲染表格 or 渲染柱状图
|
||||
console.log(this.subject.getState())
|
||||
}
|
||||
}
|
||||
|
||||
// 客户端调用
|
||||
const subject = new Subject()
|
||||
// 创建观察者
|
||||
const observer1 = new Observer(subject)
|
||||
const observer2 = new Observer(subject)
|
||||
// 更新状态
|
||||
subject.setState(10)
|
||||
```
|
||||
|
||||
## 弊端
|
||||
|
||||
不要拘泥于实现形式,比如上面代码中的例子,`subject` 与 `observer1`、`observer2` 是一对多的关系,但不一定非要用这种代码组织形式来实现观察者效果。我们也可以利用 Proxy 很轻松的实现:
|
||||
|
||||
```typescript
|
||||
const obj = new Proxy(obj, {
|
||||
get(target,key) {}
|
||||
set(target,key,value) {}
|
||||
})
|
||||
|
||||
renderTable(obj)
|
||||
renderChart(obj)
|
||||
```
|
||||
|
||||
我们可以在 `obj` 被任意一个组件访问时触发 `get`,进而对 UI 与视图进行绑定;被任意一个组件更新时触发 `set`,进而对所有使用到的视图进行刷新。使用设计模式切记不要死板,理解原理就行了,在不同平台有不同的更加优雅的实现方式。
|
||||
|
||||
## 总结
|
||||
|
||||
观察者模式是非常常用的设计模式,它描述了对象一对多依赖关系下,如何通知并更新的机制,这种机制可以用在前端的 UI 与数据映射、后端的请求与控制器映射,平台间的消息通知等大部分场景,无论现实还是程序中,存在依赖且需要通知的场景非常普遍。
|
||||
|
||||
> 讨论地址是:[精读《设计模式 - Observer 观察者模式》· Issue #302 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/302)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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,151 @@
|
||||
# State(状态模式)
|
||||
|
||||
State(状态模式)属于行为型模式。
|
||||
|
||||
**意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。**
|
||||
|
||||
简单来说,就是将 “一个大 class + 一堆 if else” 替换为 “一堆小 class”。一堆小 class 就是一堆状态,用一堆状态代替 if else 会更好拓展与维护。
|
||||
|
||||
## 举例子
|
||||
|
||||
如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。
|
||||
|
||||
### 团队接口人
|
||||
|
||||
团队是由很多同学组成的,但有一位接口人 TL,这位 TL 可能一会儿和产品经理谈需求,一会儿和其他 TL 谈规划,一会儿和 HR 谈人事,总之要做很多事情,很显然一个人是忙不过来的。TL 通过将任务分发给团队中每个同学,而不让他们直接和产品经理、其他 TL、HR 接触,那么这位 TL 的办事效率就会相当高,因为每个同学只负责一块具体的业务,而 TL 在不同时刻叫上不同的同学,让他们出面解决他们负责的专业领域问题,那么在外面看,这位 TL 团队能力很广,在内看,每个人负责的事情也比较单一。
|
||||
|
||||
### 台灯按钮
|
||||
|
||||
我们经常会看到只有一个按钮的台灯,但是可以通过按钮调节亮度,大概是如下一个循环 “关 -> 弱光 -> 亮 -> 强光 -> 关”,那么每次按按钮后,要跳转到什么状态,其实和当前状态有关。我们可以用 if else 解决这个问题,也可以用状态模式解决。
|
||||
|
||||
用状态模式解决,就是将这四个状态封装为四个类,每个类都执行按下按钮后要跳转到的状态,这样未来新增一种模式,只要改变部分类即可。
|
||||
|
||||
### 数据库连接器
|
||||
|
||||
在数据库连接前后,这个连接器的状态显然非常不同,我们如果仅用一个类描述数据库连接器,则内部免不了写大量分支语句进行状态判断。那么此时有更好的方案吗?状态模式告诉我们,可以创建多个不同状态类,比如连接前、连接中、连接后三种状态类,在不同时刻内部会替换为不同的子类,它们都继承同样的父类,所以外面看上去不需要感知内部的状态变化,内部又可以进行状态拆分,进行更好的维护。
|
||||
|
||||
## 意图解释
|
||||
|
||||
**意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。**
|
||||
|
||||
重点在 “内部状态” 的理解,也就是状态改变是由对象内部触发的,而不是外部,所以 **外部根本无需关心对象是否用了状态模式**,拿数据库连接器的例子来说,不管这个类是用 if else 堆砌的,还是用状态模式做的,都完全不妨碍它对外提供的稳定 API(接口问题),所以状态模式实质上是一种内聚的设计模式。
|
||||
|
||||
## 结构图
|
||||
|
||||
<img width=600 src="https://img.alicdn.com/imgextra/i1/O1CN01tbZ0bQ1w8xcUgbWTJ_!!6000000006264-2-tps-1350-486.png">
|
||||
|
||||
- State: 状态接口,类比为台灯状态。
|
||||
- ConcreteState: 具体状态,都继承于 State,类比为台灯的强光、弱光状态。
|
||||
|
||||
## 代码例子
|
||||
|
||||
下面例子使用 typescript 编写。
|
||||
|
||||
```typescript
|
||||
// 定义状态接口
|
||||
interface State {
|
||||
// 模拟台灯点亮
|
||||
show: () => string
|
||||
}
|
||||
|
||||
class Light1 implements State {
|
||||
constructor(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
show() {
|
||||
return '关灯'
|
||||
}
|
||||
|
||||
// 按下按钮
|
||||
public click() {
|
||||
this.context.setState(new Light2(this.context))
|
||||
}
|
||||
}
|
||||
|
||||
class Light2 implements State {
|
||||
constructor(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
show() {
|
||||
return '弱光'
|
||||
}
|
||||
|
||||
// 按下按钮
|
||||
public click() {
|
||||
this.context.setState(new Light3(this.context))
|
||||
}
|
||||
}
|
||||
|
||||
class Light3 implements State {
|
||||
constructor(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
show() {
|
||||
return '亮'
|
||||
}
|
||||
|
||||
// 按下按钮
|
||||
public click() {
|
||||
this.context.setState(new Light4(this.context))
|
||||
}
|
||||
}
|
||||
|
||||
class Light4 implements State {
|
||||
constructor(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
show() {
|
||||
return '强光'
|
||||
}
|
||||
|
||||
// 按下按钮
|
||||
public click() {
|
||||
this.context.setState(new Light1(this.context))
|
||||
}
|
||||
}
|
||||
|
||||
// 台灯
|
||||
public class Lamp {
|
||||
// 当前状态
|
||||
private currentState = new Light1(this)
|
||||
|
||||
protected setState(state: State) {
|
||||
this.currentState = state
|
||||
}
|
||||
|
||||
// 按下按钮
|
||||
public click() {
|
||||
this.getState().click()
|
||||
}
|
||||
}
|
||||
|
||||
const lamp = new Lamp() // 关闭
|
||||
lamp.click() // 弱光
|
||||
lamp.click() // 亮
|
||||
lamp.click() // 强光
|
||||
lamp.click() // 关闭
|
||||
```
|
||||
|
||||
其实有很多种方式来实现,不必拘泥于形式,大体上只要保证由多个类实现不同状态,每个类实现到下一个状态切换就好了。
|
||||
|
||||
## 弊端
|
||||
|
||||
该用 if else 的时候还是要用,不要但凡遇到 if else 就使用状态模式,那样就是书读傻了。一定要判断,是否各状态间差异很大,且使用状态模式后维护性比 if else 更好,才应该用状态模式。
|
||||
|
||||
## 总结
|
||||
|
||||
在合适场景下,状态模式可以使代码更符合开闭原则,每个类独立维护时,逻辑也更精简、聚焦,更易维护。
|
||||
|
||||
> 讨论地址是:[精读《设计模式 - State 状态模式》· Issue #303 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/303)
|
||||
|
||||
**如果你想参与讨论,请 [点击这里](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