JavaScript设计模式-( 观察者模式 与 发布、订阅模式 )

JavaScript设计模式整理


一些常见模式实现:点我

什么是设计模式?

一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题。另一种解释就是一个我们如何解决问题的模板 - 那些可以在许多不同的情况里使用的模板。

设计模式的分类:

创建型设计模式: 1、简单工厂模式 2、工厂方法模式 3、抽象工厂模式 4、建造者模式 5、原型模式 6、单例模式

结构型设计模式: 7、外观模式 8、适配器模式 9、代理模式 10、装饰者模式 11、桥接模式 12、组合模式 13、享元模式

行为型设计模式: 14、模板方法模式 15、观察者模式 16、状态模式 17、策略模式 18、职责链模式 19、命令模式 20、访问者模式 21、中介者模式 22、备忘录模式 23、迭代器模式 24、解释器模式

技巧型设计模式: 25、链模式 26、委托模式 27、数据访问对象模式 28、节流模式 29、简单模板模式 30、惰性模式 31、参与者模式 32、等待者模式

架构型设计模式: 33、同步模块模式 34、异步模块模式 35、Widget模式 36、MVC模式 37、MVP模式 38、MVVM模式

备注:该分类借鉴于《JavaScript设计模式-张容铭》

 

观察者模式

所谓观察者模式,其实就是为了实现松耦合(loosely coupled)

观察者模式指的是一个对象(Subject)维持一系列依赖于它的对象(Observer),当有关状态发生变更时 Subject 对象则通知一系列 Observer 对象进行更新。

在观察者模式中,Subject 对象拥有添加、删除和通知一系列 Observer 的方法等等,而 Observer 对象拥有更新方法等等。

在 Subject 对象添加了一系列 Observer 对象之后,Subject 对象则维持着这一系列 Observer 对象,当有关状态发生变更时 Subject 对象则会通知这一系列 Observer 对象进行更新。

用《Head First设计模式》里的气象站为例子,每当气象测量数据有更新,changed()方法就会被调用,于是我们可以在changed()方法里面,更新气象仪器上的数据,比如温度、气压等等。

但是这样写有个问题,就是如果以后我们想在changed()方法被调用时,更新更多的信息,比如说湿度,那就要去修改changed()方法的代码,这就是紧耦合的坏处。

怎么解决呢?使用观察者模式,面向接口编程,实现松耦合。

观察者模式里面,changed()方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject只需要知道,通知Observer时,需要调用哪个统一方法就好了:

(原文地址:https://juejin.im/post/6844903733738864654 )

function Subject(){
  this.observers = [];
}

Subject.prototype = {
  add:function(observer){  // 添加
    this.observers.push(observer);
  },
  remove:function(observer){  // 删除
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      if(observers[i] === observer){
        observers.splice(i,1);
      }
    }
  },
  notify:function(){  // 通知
    var observers = this.observers;
    for(var i = 0;i < observers.length;i++){
      observers[i].update();
    }
  }
}

function Observer(name){
  this.name = name;
}

Observer.prototype = {
  update:function(){  // 更新
    console.log('my name is '+this.name);
  }
}

var sub = new Subject();

var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');

sub.add(obs1);
sub.add(obs2);
sub.notify();  //my name is ttsy1、my name is ttsy2

上述代码中,我们创建了 Subject 对象和两个 Observer 对象,当有关状态发生变更时则通过 Subject 对象的 notify 方法通知这两个 Observer 对象,这两个 Observer 对象通过 update 方法进行更新。

在 Subject 对象添加了一系列 Observer 对象之后,还可以通过 remove 方法移除某个 Observer 对象对它的依赖。

var sub = new Subject();

var obs1 = new Observer('ttsy1');
var obs2 = new Observer('ttsy2');

sub.add(obs1);
sub.add(obs2);
sub.remove(obs2);
sub.notify();  //my name is ttsy1

发布订阅模式

发布订阅模式指的是希望接收通知的对象(Subscriber)基于一个主题通过自定义事件订阅主题,被激活事件的对象(Publisher)通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。

function EventTarget(){
            //EventTart类型有一个单独的属性handlers,用于存储事件处理程序
            this.handlers = {};
        }

        EventTarget.prototype = {
            //重新指向EventTarget函数
            constructor: EventTarget,
            //type:事件类型 handler: 用于处理该事件的函数
            addHandler:function(type,handler){
                //判断handlers对象中是否存在属性名为type的属性
                if(typeof this.handlers[type] == "undefined"){
                    //如果不存在就将属性名为type属性的属性值设置为一个新数组,用于存放type类型的事件处理程序
                    this.handlers[type] = [];
                }
                //将handler事件处理函数添加到数组末尾
                this.handlers[type].push(handler);
            },

            //fire()函数要接受一个至少包含type属性的对象作为参数
            fire: function(event){
                if(!event.target){
                    event.target = this;
                }
                //判断要触发的消息是否存在
                if(this.handlers[event.type] instanceof Array){
                    //获取对应类型的事件处理程序
                    var handlers = this.handlers[event.type];
                    for(var i=0,len = handlers.length; i<len; i++){
                        //调用每个函数,并给出event对象
                        handlers[i](event);
                    }
                }
            },

            removeHandler: function(type, handler){
                //防止删除的消息不存在的情况
                if(this.handlers[type] instanceof Array){
                    var handlers = this.handlers[type];
                    for(var i=0,len = handlers.length;i<len;i++){
                        if(handlers[i] === handler){
                            break;
                        }
                    }
                    //将i位置的事件处理程序删除
                    handlers.splice(i,1);
                }
            }
        };

EventTarget类型有一个单独的属性handlers,用于储存事件处理程序。还有三个方法:                                                        

1.  addHandler(),用于注册给定类型事件的事件处理程序;  
2.  fire(),用于触发一个事件;
3.  removeHandler(),用于注销某个事件类型的事件处理程序。

addHandler()方法接受两个参数: 事件类型和用于处理该事件的函数。当调用该方法时,会进行一次检查,看看handlers属性中是否已经存在一个针对该事件类型的数组;如果没有,则创建一个新的。然后使用push()将该处理程序添加到数组的末尾。

如果要触发一个事件,要调用fire()函数。该方法接受一个单独的参数,是一个至少包含type属性的对象。fire()方法给 event对象设置一个 target属性,如果它尚未被指定的话。然后它就查找对应该事件类型的一组处理程序,调用各个函数,并给出event对象。因为这些都是自定义事件,所以 event 对象上还需要的额外信息由你自己决定.

removeHandler()方法是 addhandler()的辅助,它们接受的参数一样:事件的类型和事件处理程序。这个方法搜索事件处理程序的数组找到要删除的处理程序的位置。如果找到了,则使用 break操作符退出for循环。然后使用splice方法将该项目从数组中删除.
    然后使用EventTarget类型的自定义事件可以如下使用:

 function handleMessage(event){
        alert("message received:" + event.message);
    }
    //创建一个新对象
    var target = new EventTarget();
    //添加一个事件处理程序
    target.addHandler("message",handleMessage);
    //触发事件
    target.fire({type:"message",message:"Hello world!"});
    //删除事件处理程序
    target.removeHandler("message",handleMessage);
    //再次触发,应该没有处理程序
    target.fire({type:"message",message:"Hello world!"});

在这段代码中,定义了 handleMessage()函数用于处理 message事件。它接受 event对象并输出 message属性。调用 target对象的 addHandler()方法并传给" message以及 handleMessage()函数。在接下来的一行上,调用了fire()函数,并传递了包含2个属性,即type和 message的对象直接量。它会调用 message事件的事件处理程序,这样就会显示一个警告框(来自 handleMessage())
然后删除了事件处理程序,这样即使事件再次触发,也不会显示任何警告框。
    因为这种功能是封装在一种自定义类型中的,其他对象可以继承 EventTarget并获得这个行为,如下所示:

function Person(name , age){
        EventTarget.call(this);
        this.name = name;
        this.age = age;
    }


    //组合继承方法
    // function inheritPrototype(subType,superType){
    //     var prototype = object(superType.prototype);
    //     prototype.constructor = subType;
    //     subType.prototype = prototype;
    // }

    inheritPrototype(Person,EventTargetart);

    Person.prototype.say = function(message){
        this.fire({type:"message",message:'message'});
    };

Person类型使用了寄生组合继承方法来继承 EventTarget。一旦调用了say()方法,便触发了事件,它包含了消息的细节。在某种类型的另外的方法中调用fire()方法是很常见的,同时它通常不是公开调用的。这段代码可以照如下方式使用:

function handleMessage (event)(
alert (event. target name says
event message)i
//创建新 person
var person: new Person("Nicholas" 29)
//添加一个事件处理程序
person addHandler("message", handleMessage)
//在该对象上调用1个方法,它触发消息事件
person say("Hi there.");

这个例子中的handleMessage()函数显示了某人名字(通过 event.target.name获得)的一个警告框和消息正文。当调用say()方法并传递一个消息时,就会触发 message事件。接下来,它又会调用 handleMessage()函数并显示警告框。

当代码中存在多个部分在特定时刻相互交互的情况下,自定义事件就非常有用了。这时,如果每全对象都有对其他所有对象的引用,那么整个代码就会紧密耦合,同时维护也变得很困难,因为对某个对象的修改也会影响到其他对象,使用自定义事件有助于解耦相关对象,保持功能的隔绝。在很多情况中触发事件的代码和监听事件的代码是完全分离的

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

互不相识?那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker。

发布者只需告诉Broker,我要发的消息,topic是AAA;

订阅者只需告诉Broker,我要订阅topic是AAA的消息;

于是,当Broker收到发布者发过来消息,并且topic是AAA时,就会把消息推送给订阅了topic是AAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

放一张极简的图,给大家对比一下这两个模式的区别:

 

总结

从表面上看:

  • 观察者模式里,只有两个角色 —— 观察者 + 被观察者
  • 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个经常被我们忽略的 —— 经纪人Broker

往更深层次讲:

  • 观察者和被观察者,是松耦合的关系
  • 发布者和订阅者,则完全不存在耦合

从使用层面上讲:

  • 观察者模式,多用于单个应用内部
  • 发布订阅模式,则更多的是一种跨应用的模式(cross-application pattern),比如我们常用的消息中间件

此部分参考:https://juejin.im/post/6844903733738864654 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1planet

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值