关于typescript:TypeScript-设计模式之观察者模式

40次阅读

共计 5920 个字符,预计需要花费 15 分钟才能阅读完成。

一、模式介绍

1. 背景介绍

在软件系统中常常碰到这类需要:当一个对象的状态产生扭转,某些与它相干的对象也要随之做出相应的变动。这是建设一种 对象与对象之间的依赖关系 ,一个对象产生扭转时将 主动告诉其余对象 ,其余对象将 相应做出反馈

咱们将产生扭转的对象称为 察看指标 ,将被告诉的对象称为 观察者 一个察看指标能够对应多个观察者,而且这些观察者之间没有互相分割,之后能够依据须要减少和删除观察者,使得零碎更易于扩大,这就是观察者模式的产生背景。

2. 概念介绍

观察者模式 (Observer Pattern):定义对象间的一种 一对多依赖关系,使得每当一个对象状态产生扭转时,其相干依赖对象皆失去告诉并被自动更新。观察者模式是一种对象行为型模式。

3. 生存场景

在所有浏览器事件(鼠标悬停,按键等事件)都是观察者模式的例子。

另外还有:

如咱们订阅微信公众号“前端自习课”(察看指标 ),当“前端自习课”群发图文音讯后,所有公众号粉丝( 观察者)都会接管到这篇文章(事件),这篇文章的内容是发布者自定义的(自定义事件),粉丝浏览后作出特定操作(如:点赞,珍藏,关注等)。

二、模式特点

1. 模式组成

在观察者模式中,通常蕴含以下角色:

  • 指标:Subject
  • 察看指标:ConcreteSubject
  • 观察者:Observer
  • 具体观察者:ConcreteObserver

2. UML 类图

图片起源:《TypeScript 设计模式之观察者模式》

3. 长处

  • 观察者模式能够实现 表示层和数据逻辑层的拆散 ,并 升高察看指标和观察者之间耦合度
  • 观察者模式反对 简略播送通信 主动告诉 所有曾经订阅过的对象;
  • 观察者模式 合乎“开闭准则”的要求
  • 察看指标和观察者之间的形象耦合关系可能 独自扩大以及重用

4. 毛病

  • 当一个察看指标 有多个间接或间接的观察者 时,告诉所有观察者的过程将会破费很多工夫;
  • 当察看指标和观察者之间存在 循环依赖 时,察看指标会触发它们之间进行循环调用,可能 导致系统解体
  • 观察者模式短少相应机制,让观察者晓得所察看的指标对象是怎么发生变化的,而仅仅只是晓得察看指标产生了变动。

三、应用场景

在以下状况下能够应用观察者模式:

  • 在一个形象模型中,一个对象的行为 依赖于 另一个对象的状态。即当 指标对象 的状态产生扭转时,会间接影响到 观察者 的行为;
  • 一个对象须要告诉其余对象产生反馈,但不晓得这些对象是谁。
  • 须要在零碎中创立一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……,能够应用观察者模式创立一种链式触发机制。

四、实战示例

1. 简略示例

  1. 定义 察看指标接口 (Subject)和 观察者接口(Observer)
// ObserverPattern.ts

// 察看指标接口
interface Subject {addObserver: (observer: Observer) => void;
  deleteObserver: (observer: Observer) => void;
  notifyObservers: () => void;}

// 观察者接口
interface Observer {notify: () => void;
}
  1. 定义 具体察看指标类(ConcreteSubject)
// ObserverPattern.ts

// 具体察看指标类
class ConcreteSubject implements Subject{private observers: Observer[] = [];
    
  // 增加观察者
  public addObserver(observer: Observer): void {console.log(observer, "is pushed~~");
    this.observers.push(observer);
  }

  // 移除观察者
  public deleteObserver(observer: Observer): void {console.log(observer, "have deleted~~");
    const idx: number = this.observers.indexOf(observer);
    ~idx && this.observers.splice(idx, 1);
  }

  // 告诉观察者
  public notifyObservers(): void {console.log("notify all the observers", this.observers);
    this.observers.forEach(observer => { 
      // 调用 notify 办法时能够携带指定参数
      observer.notify();});
  }
}
  1. 定义 具体观察者类(ConcreteObserver)
// ObserverPattern.ts

// 具体观
class ConcreteObserver implements Observer{constructor(private name: string) {}

  notify(): void {
    // 能够解决其余逻辑
    console.log(`${this.name} has been notified.`);
  }
}
  1. 测试代码
// ObserverPattern.ts

function useObserver(): void {const subject: Subject = new ConcreteSubject();
  const Leo   = new ConcreteObserver("Leo");
  const Robin = new ConcreteObserver("Robin");
  const Pual  = new ConcreteObserver("Pual");
  const Lisa  = new ConcreteObserver("Lisa");

  subject.addObserver(Leo);
  subject.addObserver(Robin);
  subject.addObserver(Pual);
  subject.addObserver(Lisa);
  subject.notifyObservers();
  
  subject.deleteObserver(Pual);
  subject.deleteObserver(Lisa);
  subject.notifyObservers();}

useObserver();

残缺演示代码如下:

// ObserverPattern.ts

interface Subject {addObserver: (observer: Observer) => void;
  deleteObserver: (observer: Observer) => void;
  notifyObservers: () => void;}

interface Observer {notify: () => void;
}

class ConcreteSubject implements Subject{private observers: Observer[] = [];

  public addObserver(observer: Observer): void {console.log(observer, "is pushed~~");
    this.observers.push(observer);
  }

  public deleteObserver(observer: Observer): void {console.log(observer, "have deleted~~");
    const idx: number = this.observers.indexOf(observer);
    ~idx && this.observers.splice(idx, 1);
  }

  public notifyObservers(): void {console.log("notify all the observers", this.observers);
    this.observers.forEach(observer => { 
      // 调用 notify 办法时能够携带指定参数
      observer.notify();});
  }
}

class ConcreteObserver implements Observer{constructor(private name: string) {}

  notify(): void {
    // 能够解决其余逻辑
    console.log(`${this.name} has been notified.`);
  }
}

function useObserver(): void {const subject: Subject = new ConcreteSubject();
  const Leo   = new ConcreteObserver("Leo");
  const Robin = new ConcreteObserver("Robin");
  const Pual  = new ConcreteObserver("Pual");
  const Lisa  = new ConcreteObserver("Lisa");

  subject.addObserver(Leo);
  subject.addObserver(Robin);
  subject.addObserver(Pual);
  subject.addObserver(Lisa);
  subject.notifyObservers();
  
  subject.deleteObserver(Pual);
  subject.deleteObserver(Lisa);
  subject.notifyObservers();}

useObserver();

2. Vue.js 数据双向绑定实现原理

在 Vue.js 中,当咱们批改数据状时,视图随之更新,这就是 Vue.js 的双向数据绑定(也称响应式原理),这是 Vue.js 中最独特的个性之一。
如果你对 Vue.js 的双向数据绑定还不分明,倡议先浏览官网文档《深刻响应式原理》章节。

2.1 原理介绍

在官网中提供这么一张流程图,介绍了 Vue.js 响应式零碎的整个流程:


图片来自:Vue.js 官网《深刻响应式原理》

在 Vue.js 中,每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”(“Touch”过程)过的数据 property 记录为依赖(Collect as Dependency 过程)。之后当依赖项的 setter 触发时,会告诉 watcher(Notify 过程),从而使它关联的组件从新渲染(Trigger re-render 过程)——这是一个典型的观察者模式。

这道面试题考查面试者对 Vue.js 底层原理的了解、对观察者模式的实现能力以及一系列重要的 JS 知识点,具备较强的综合性和代表性。

2.2 组成部分

在 Vue.js 数据双向绑定的实现逻辑中,蕴含三个要害角色:

  • observer(监听器):这里的 observer 不仅是订阅者(须要监听数据变动 ),同时还是发布者( 对监听的数据进行转发)。
  • watcher(订阅者):watcher 对象是 真正的订阅者,observer 把数据转发给 watcher 对象。watcher 接管到新的数据后,执行视图更新。
  • compile(编译器):MVVM 框架特有的角色,负责对每个节点元素指令进行扫描和解析,解决指令的数据初始化、订阅者的创立等操作。

这三者的配合过程如图所示:

图片来自:掘金小册《JavaScript 设计模式核⼼原理与应⽤实际》

2.3 实现外围代码 observer

首先咱们须要实现一个办法,这个办法会对须要监听的数据对象进行遍历、给它的属性加上定制的 gettersetter 函数。这样凡是这个对象的某个属性产生了扭转,就会触发 setter 函数,进而告诉到订阅者。这个 setter 函数,就是咱们的监听器:

// observe 办法遍历并包装对象属性
function observe(target) {
    // 若 target 是一个对象,则遍历它
    if(target && typeof target === 'object') {Object.keys(target).forEach((key)=> {
            // defineReactive 办法会给指标属性装上“监听器”defineReactive(target, key, target[key])
        })
    }
}
// 定义 defineReactive 办法
function defineReactive(target, key, val) {
    // 属性值也可能是 object 类型,这种状况下须要调用 observe 进行递归遍历
    observe(val)
    // 为以后属性装置监听器
    Object.defineProperty(target, key, {
         // 可枚举
        enumerable: true,
        // 不可配置
        configurable: false, 
        get: function () {return val;},
        // 监听器函数
        set: function (value) {console.log(`${target}属性的 ${key}属性从 ${val}值变成了了 ${value}`)
            val = value
        }
    });
}

上面实现订阅者 Dep

// 定义订阅者类 Dep
class Dep {constructor() {
        // 初始化订阅队列
        this.subs = []}
    
    // 减少订阅者
    addSub(sub) {this.subs.push(sub)
    }
    
    // 告诉订阅者(是不是所有的代码都似曾相识?)notify() {this.subs.forEach((sub)=>{sub.update()
        })
    }
}

当初咱们能够改写 defineReactive 中的 setter 办法,在监听器里去告诉订阅者了:

function defineReactive(target, key, val) {const dep = new Dep()
    // 监听以后属性
    observe(val)
    Object.defineProperty(target, key, {set: (value) => {
            // 告诉所有订阅者
            dep.notify()}
    })
}

五、总结

观察者模式又称公布 - 订阅模式、模型 - 视图模式、源 - 监听器模式或隶属者模式。是一种 对象行为型模式 。其定义了一种 对象间的一对多依赖关系,当察看指标产生状态变动,会告诉所有观察者对象,使它们自动更新。

在理论业务中,如果一个对象的行为 依赖于 另一个对象的状态。或者说当 指标对象 的状态产生扭转时,会间接影响到 观察者 的行为,尽量思考到应用观察者模式来实现。

六、拓展

观察者模式和公布 - 订阅模式两者很像,但其实区别比拟大。例如:

  • 耦合度差别:观察者模式的耦合度就比公布 - 订阅模式要高;
  • 关注点不同:观察者模式须要晓得彼此的存在,而公布 - 订阅模式则是通过调度核心来分割公布 / 订阅者。

下一篇文章见。

参考文章

1.《3. 观察者模式》
2.《TypeScript 设计模式之观察者模式》
3.《JavaScript 设计模式核⼼原理与应⽤实际》

正文完
 0