从命令式到响应式 (二)

数据结
• 阅读 1672

知识点回顾,上次主要说了函数式和面向对象,命令式和响应式,push 系统和 pull 系统的差别。在编程范式,风格之外,设计模式也是在程序设计中时时刻刻都在使用的东西,今天主要就讨论一下设计模式这个东西。

什么是设计模式

模式是一种可复用的解决方案,它有三大好处:

  1. 模式是已经得到验证的解决方案,因此我们可以在适合的场景中放心的使用它。
  2. 模式很容易被复用,是一种立即可用的解决方案,而且可以对其适当的修改以满足个性化的需求。
  3. 模式富有表达力,它通常有很良好的结构及已经设置好的表达方案的词汇,可以非常轻松的表达出程序员的意图。

其实我们每天都在接触模式,从最简单的facade(外观模式,jQuery,lodash为代表)到 singleton,再到MVC,模式可以说无处不再,当然还有rxjs中使用观察者模式等。有模式,就有反模式,既然模式可以带来好处,相应的反模式就会带来坏处,在javaScript中,以下就是我们经常见到的反模式:

  1. 在全局上下文中定义大量的变量来污染全局命名空间。这里的全局我们应该以相对的思维考虑,而不是特指window对象。例如:在angularjs 中一个controller 作为封闭的作用域,对于它内部定义的各种变量来说,这个作用域就是全局的,写过angularjs的同学应该遇到过,在一个controller中定义很多的变量,然后随着功能的增加,越来越难以维护。
  2. 修改类的原型,尤其是Object类,比修改更过份的是直接替换。
  3. 以内联的形式使用javaScript。
  4. 给setTimeout 或 setInterval传递字符串而不是函数,这会触发内部 eval 的执行。

响应式中的设计模式

观察者模式

在这种模式中,一个对象维持一系列依赖于它的对象,将有关的状态变更自动的通知给它们。当我们不再希望某个特定的观察者获取注册目标的对象时,它可以从目标的观察者列表中移除。代码如下:

观察者列表类,我们利用这个类来维护观察者的增删改查

class ObserverList {
    constructor() { };

    list = [];

    add(observer) {
        this.list.push(observer);
    }

    count() {
        return this.list.length;
    }

    get(index) {
        if(index > -1 && index < this.list.length) {
            return this.list[index];
        }else {
            return null;
        }
    }

    indexOf(observer, startIndex) {
        let i = startIndex;

        let pointer = -1;

        while( i< this.list.length) {
            if(this.list[i] === observer) {
                pointer = i;
            }
            i++;
        }

        return pointer;
    }

    removeIndexAt(index) {
        if(index === 0) {
            this.list.shift();
        }else if (index === this.list.length - 1) {
            this.list.pop();
        }
    }
}

主题类,利用这个类来维护一个观察目标,使用观察者列表类来维护其自己的观察者,通过观察者提供的接口向外发送目标上发生的变化。

class Subject {
    constructor() {
        this.observers = new ObserverList();
    }

    addObserver(observer) {
        this.observers.add(observer);
    }

    removeObserver(observer) {
        this.observers.removeIndexAt(this.observers.indexOf(observer, 0));
    }

    notify(context) {
        const count = this.observers.count();

        for(let i = 0; i< count; i++) {
            this.observers.get(i).update(context);
        }
    }
}

观察者类,为目标发生变化时需要获得通知的对象提供一个更新接口。

class Observer {
    constructor() { }

    update() {
      // 获取通知的接口, 不同的observe 可以针对性的设置更新逻辑
    }
}

然后我们就可以利用定义好的这些类,实现一些功能,例如,一个主checkbox,当它的状态变化时通知页面上其它的checkbox检查状态,代码大致如下:

HTML代码

<button id="button">Add Observer</button>
<input id="box" type="checkbox">
<div id="container"></div>

javaScript代码

const box = document.getElementById('box');
const btn = document.getElementById('button');
const container = document.getElementById('container');

// 工具函数
function extend(source, target) {
    for (let key in source) {
        target[key] = source[key];
    }
}

// 利用工具函数来扩展DOM元素
extend(new Subject(), box);

// 将点击事件通知给观察者
box.onclick = function {
    box.notify(box.checked);
}

btn.onclick = function addNewObserver() {
    const check = document.createElement('input');

    check.type = 'checkbox';

    extend(new Observer(), check);

    // 重写自定义的更新行为
    check.update = (value) => this.checked = value;

    // 为subject的观察者列表中添加新的观察者
    box.addObserver(check);

    // 将观察者附加到容器上
    container.appendChild(check);
}

发布/订阅模式

观察者模式要求希望接收通知的观察者必须订阅内容改变的事件,而发布/订阅模式中添加了一个事件通道,此通道介于订阅者和发布者之间,这样设置的主要目的是促进发布者和接收者之间的松散耦合,避免订阅者和发布者产和直接的联系,如图:

Observer Pattern

      /----<--Subscribe--<--\
    Subject             Observer
      \--->--Fire Event-->--/

Publish/Subscribe Pattern

                                    /----<--Subscribe--<--\
    Publisher-->--publish-->---Event Channel         Subscriber
                                    \--->---fire event-->--/

在实际的应用中, 这两种模式可以结合使用,它们都鼓励开发者思考应用程序之间不同部分之间的关系,将应用程序分解为更小,更松散耦合的块以提高代码的复用。

这两种模式的优缺点

优点

  1. 只需要维护各个对象之间的通信接口的一致性,而无需紧密耦合。
  2. 观察者和目标之间可以建立起一种动态的关系,这提供了很大的灵活性,在程序的各部分紧密耦合时,要实现这种动态关系是非常不容易的。

缺点

  1. 在发布/订阅模式中,由于解耦了发布者和订阅者,有时会难以保证程序按照我们的预期进行。例如,发布者会假设有人在订阅它们,当订阅者发生错误后,由于系统的解耦,发布者并不会看到这一点。
  2. 订阅者之间非常无视彼此的存在,对于变换发布者产生的成本视而不见,由于它们之间动态的关系,难以跟踪依赖更新。

rxjs的实现就是建立在这两种模式的基础之上,预先要了解的基本知识通过这两篇基本上介绍完了,当然都是走马观花式的,基中任何一个点拿出来都可以长篇大论,各位如有兴趣可以找资料深入研究。

从命令式到响应式 (二)

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
3A网络 3A网络
3年前
Golang 常见设计模式之单例模式
之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非
Easter79 Easter79
4年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
4年前
JavaScript的入门简介
什么是JavaScriptJavaScript,我们一般简称为JS,是一种具有函数优先的轻量级,解释型或即时编译型的编程语言。JavaScript现在已经被用到了很多非浏览器环境中,JavaScript基于原型编程、多范式的动态脚本语言,并支持面向对象、命令式和声明式风格。HTML、CSS、JavaScript三者不同的功能:
Stella981 Stella981
4年前
JavaScript面向对象编程的15种设计模式
在程序设计中有很多实用的设计模式,而其中大部分语言的实现都是基于“类”。在JavaScript中并没有类这种概念,面向对象编程不是基于类,而是基于原型去面向对象编程,JS中的函数属于一等对象,而基于JS中闭包与弱类型等特性,在实现一些设计模式的方式上与众不同。ps:本文之讲述面向对象编程的设计模式策略,JavaScript原型的基础请参考阮一峰面向
Wesley13 Wesley13
4年前
00_设计模式之语言选择
设计模式之语言选择设计模式简介背景设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。设计模式(Designpattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的
Wesley13 Wesley13
4年前
#分布式系统架构之# 事件驱动模式以及与之匹配的长时间处理过程讨论
     在分布式系统下,可以很多种架构从事设计,或者分布式系统对技术架构本身没有做严格的限制。但是结合自己的实践以及基于《领域驱动设计》的推荐,采用【事件驱动模式】是比较好的一种分布式系统架构方式。该模式充分实现了不同系统之间的代码解耦,所有的业务流转是通过事件广播进行驱动的。所有业务都是在针对名为【事件总线】的组件在编程,也无需知道事件的生产者
Easter79 Easter79
4年前
SwiftUI 跨组件数据传递
作者:Cyandev,iOS和MacOS开发者,目前就职于字节跳动0x00前言众所周知,SwiftUI的开发模式与React、Flutter非常相似,即都是声明式UI,由数据驱动(产生)视图,视图也会与数据自动保持同步,框架层会帮你处理“绑定”的问题。在声明式UI中不存在命令式地让一个视图变成xxx
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究