从命令式到响应式 (二)

知识点回顾,上次主要说了函数式和面向对象,命令式和响应式,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代码



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

你可能感兴趣的:(javascript,rxjs,angular5)