设计模式 - 发布-订阅者模式

1、发布-订阅者 设计模式

定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知

观察者模式和发布订阅模式区别

观察者模式是由具体目标(发布者/被观察者)调度的,而发布/订阅模式是由独立的调度中心进行调度,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会;可以说发布订阅模式是观察者模式进一步解耦,在实际中被大量运用的一种模式

** 观察者模式 **

1.定义/解析

目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

2.调度图/流程图

3.实现代码

//观察者列表
function ObserverList(){
  this.observerList = [];
}
ObserverList.prototype.add = function( obj ){
  return this.observerList.push( obj );
};
ObserverList.prototype.count = function(){
  return this.observerList.length;
};
ObserverList.prototype.get = function( index ){
  if( index > -1 && index < this.observerList.length ){
    return this.observerList[ index ];
  }
};
ObserverList.prototype.indexOf = function( obj, startIndex ){
  var i = startIndex;
  while( i < this.observerList.length ){
    if( this.observerList[i] === obj ){
      return i;
    }
    i++;
  }
  return -1;
};
ObserverList.prototype.removeAt = function( index ){
  this.observerList.splice( index, 1 );
};

//目标
function Subject(){
  this.observers = new ObserverList();
}
Subject.prototype.addObserver = function( observer ){
  this.observers.add( observer );
};
Subject.prototype.removeObserver = function( observer ){
  this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};
Subject.prototype.notify = function( context ){
  var observerCount = this.observers.count();
  for(var i=0; i < observerCount; i++){
    this.observers.get(i).update( context );
  }
};

//观察者
function Observer(){
  this.update = function(){
    // ...
  };
}

4.观察者组成(java):

  • 抽象主题角色:把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删* 除观察者角色。一般用一个抽象类和接口来实现。
  • 抽象观察者角色:为所有具体的观察者定义一个接口,在得到主题的通知时更新自己。
  • 具体主题角色:在具体主题内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个子类实现。
  • 具体观察者角色:该角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个子类实现。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

5.类图(java)

简化版:

6.时序图(java)

7.java实现代码

/*抽象观察者(Observer)*/
public interface Observer {
    public void update(String message);
}

/*具体观察者(ConcrereObserver):实现抽象接口*/
public class WeixinUser implements Observer {
    // 微信用户名
    private String name;
    public WeixinUser(String name) {
        this.name = name;
    }
    @Override
    public void update(String message) {
        System.out.println(name + "-" + message);
    }
}

/*抽象被观察者/目标对象/主题(Subject)*/
public interface Subject {
    /**
     * 增加订阅者
     * @param observer
     */
    public void attach(Observer observer);
    /**
     * 删除订阅者
     * @param observer
     */
    public void detach(Observer observer);
    /**
     * 通知订阅者更新消息
     */
    public void notify(String message);
}

/*具体被观察者/目标对象/主题(Subject)*/
public class SubscriptionSubject implements Subject {
    //储存订阅公众号的微信用户
    private List<Observer> weixinUserlist = new ArrayList<Observer>();

    @Override
    public void attach(Observer observer) {
        weixinUserlist.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        weixinUserlist.remove(observer);
    }

    @Override
    public void notify(String message) {
        for (Observer observer : weixinUserlist) {
            observer.update(message);
        }
    }
}

/*客户端调用*/
public class Client {
    public static void main(String[] args) {
        SubscriptionSubject mSubscriptionSubject=new SubscriptionSubject();
        //创建微信用户
        WeixinUser user1=new WeixinUser("杨影枫");
        WeixinUser user2=new WeixinUser("月眉儿");
        WeixinUser user3=new WeixinUser("紫轩");
        //订阅公众号
        mSubscriptionSubject.attach(user1);
        mSubscriptionSubject.attach(user2);
        mSubscriptionSubject.attach(user3);
        //公众号更新发出消息给订阅的微信用户
        mSubscriptionSubject.notify("刘望舒的专栏更新了");
    }
}

** 发布/订阅模式 **

1.定义/解析

订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(顺带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码

2.调度图/流程图

3.实现代码

// 1.javascript经典版
var pubsub = {};
(function(myObject) {
    // Storage for topics that can be broadcast
    // or listened to
    var topics = {};
    // An topic identifier
    var subUid = -1;
    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    myObject.publish = function( topic, args ) {
        if ( !topics[topic] ) {
            return false;
        }
        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;
        while (len--) {
            subscribers[len].func( topic, args );
        }
        return this;
    };
    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    myObject.subscribe = function( topic, func ) {
        if (!topics[topic]) {
            topics[topic] = [];
        }
        var token = ( ++subUid ).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };
    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    myObject.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                    if ( topics[m][i].token === token ) {
                        topics[m].splice( i, 1 );
                        return token;
                    }
                }
            }
        }
        return this;
    };
}( pubsub ));

// 2.javascript通用版
var event = {
        clientList: [], //订阅者列表
        listen: function( key, fn ){ // 发布者监听并订阅事件
                if ( !this.clientList[ key ] ){
                        this.clientList[ key ] = [];
                }
                this.clientList[key].push(fn) // 订阅的消息添加进缓存列表
        },
        trigger: function() { // 触发订阅事件
                var key = Array.prototype.shift.call( arguments ), // (1);
                fns = this.clientList[ key ];
                if ( !fns || fns.length === 0 ){ // 如果没有绑定对应的消息
                        return false;
                }
                for( var i = 0, fn; fn = fns[ i++ ]; ){
                        fn.apply( this, arguments ); // (2) // arguments 是 trigger 时带上的参数
                }
        },
        remove: function( key, fn ){ // 取消订阅
                var fns = this.clientList[ key ];
                if ( !fns ){ // 如果 key 对应的消息没有被人订阅,则直接返回
                        return false;
                }
                if ( !fn ){ // 如果没有传入具体的回调函数,表示需要取消 key 对应消息的所有订阅
                        fns && ( fns.length = 0 );
                }else{
                        for ( var l = fns.length - 1; l >=0; l-- ){ // 反向遍历订阅的回调函数列表
                                var _fn = fns[ l ];
                                if ( _fn === fn ){
                                    fns.splice( l, 1 );
                                }
                        }
                }
        }
} 

// 这个函数可以给所有的对象都动态安装发布—订阅功能:
var installEvent = function( obj ){
        for ( var i in event ){
                obj[ i ] = event[ i ];
        }
};

4.实例:

var login = {}
installEvent(login) //  实例化发布-订阅对象

$.ajax( ‘http:// xxx.com?login‘, function(data){ // 登录成功
        login.trigger( ‘loginSucc‘, data); // 发布登录成功的消息 data
});

var header = (function(){ // header 模块
        login.listen( ‘loginSucc‘, function( data){
        header.setAvatar( data.avatar );
        });
        return {
                setAvatar: function( data ){
                        // 具体操作代码
                        console.log( ‘设置 header 模块的头像‘ );
                }
        }
})();

var nav = (function(){ // nav 模块
        login.listen( ‘loginSucc‘, function( data ){
                监听到登录成功事件后回调操作(具体见return中)
                nav.setAvatar( data.avatar );
        });
        return {
                setAvatar: function( avatar ){
                        console.log( ‘设置 nav 模块的头像‘ );
                }
        }
})();

5.加强版

********
 Event 对象 添加以下功能
 1、提供创建命名空间的功能
 2、可先发布,再订阅
********

var Event = (function(){
     var global = this,
                Event,
                _default = ‘default‘;

    Event = function(){
                var _listen,
                _trigger,
                _remove,
                _slice = Array.prototype.slice,
                _shift = Array.prototype.shift,
                _unshift = Array.prototype.unshift,
                namespaceCache = {},
                _create,
                find,
                each = function( ary, fn ){
                    var ret;
                    for ( var i = 0, l = ary.length; i < l; i++ ){
                        var n = ary[i];
                        ret = fn.call( n, i, n);
                    }
                    return ret;
                };
                _listen = function( key, fn, cache ){
                    if ( !cache[ key ] ){
                        cache[ key ] = [];
                    }
                    cache[key].push( fn );
                };
                _remove = function( key, cache, fn){
                     if ( cache[ key ] ){
                         if( fn ){
                            for( var i = cache[ key ].length; i >= 0; i-- ){
                                if( cache[ key ][i] === fn) {
                                    cache[ key ].splice(i, 1)
                                }
                            }
                         } else {
                            cache[ key ] = []
                         }
                     }
                };
                _trigger = function(){
                        var cache = _shift.call(arguments),
                                key = _shift.call(arguments),
                                args = arguments,
                                _self = this,
                                ret,
                                stack = cache[ key ]; 

                        if (!stack || !stack.length ){
                                return;
                        }
                        return each( stack, function(){
                            return this.apply( _self, args );
                        });
                };
                _create = function( namespace ){
                        var namespace = namespace || _default;
                        var cache = {},
                                offlineStack = [],
                                ret = {
                                        listen: function( key, fn, last ){
                                                _listen(key, fn, cache);
                                                if ( offlineStack === null ){
                                                        return;
                                                }
                                                if ( last === ‘last‘ ){
                                                        offlineStack.length && offlineStack.pop()();
                                                }else{
                                                        each( offlineStack, function(){
                                                                this();
                                                        });
                                                }

                                                offlineStack = null;
                                        },
                                        one: function( key, fn, last ){
                                                _remove( key, cache );
                                                this.listen( key, fn ,last );
                                        },
                                        remove: function( key, fn ){
                                                _remove( key, cache ,fn);
                                        },
                                        trigger: function(){
                                                var fn, args,
                                                _self = this;
                                                _unshift.call( arguments, cache );
                                                args = arguments;
                                                fn = function(){
                                                        return _trigger.apply( _self, args );
                                                };
                                                if ( offlineStack ){
                                                        return offlineStack.push( fn );
                                                }
                                                return fn();
                                        }
                                };
                        return namespace ? ( namespaceCache[ namespace ] ? namespaceCache[ namespace ] :namespaceCache[ namespace ] = ret ) : ret;
                };
             return {
                        create: _create,
                        one: function( key,fn, last ){
                                var event = this.create();
                                event.one( key,fn,last );
                        },
                        remove: function( key,fn ){
                                var event = this.create(); event.remove( key,fn );
                        },
                        listen: function( key, fn, last ){
                                var event = this.create();
                                event.listen( key, fn, last );
                        },
                        trigger: function(){
                                var event = this.create( );
                                event.trigger.apply( this, arguments );
                        }
                };
        }();
        return Event;
})();

注:在java中,通常会把订阅者对象自身当成引用传入发布者对象中,同时订阅者对象还需提供一个名为诸如 update 的方法,供发布者对象在适合的时候调用。而在 JavaScript 中,我们用注册回调函数的形式来代替传统的发布-订阅模式

使用场景

前端 JS中的DOM事件的事件回调

前端框架 vue 双向数据绑定的实现 defineProperty + 发布-订阅者模式 等等

原文地址:https://www.cnblogs.com/136asdxxl/p/9783657.html

时间: 2024-10-18 13:29:54

设计模式 - 发布-订阅者模式的相关文章

设计模式-发布订阅模式(javaScript)

1. 前言 2. 什么是发布订阅模式 3. 发布订阅优缺点 4. 举例 4. 总结 1. 前言 发布订阅者模式是为了发布者和订阅者之间避免产生依赖关系,发布订阅者之间的订阅关系由一个中介列表来维护.发布者只需做好发布功能,至于订阅者是谁,订阅者做了什么事情,发布者是无需关心的 2. 什么是发布订阅模式 发布订阅:是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者).而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在.同样的,订阅者可以表达

ActiveMQ发布-订阅消息模式

一.订阅杂志我们很多人都订过杂志,其过程很简单.只要告诉邮局我们所要订的杂志名.投递的地址,付了钱就OK.出版社定期会将出版的杂志交给邮局,邮局会根据订阅的列表,将杂志送达消费者手中.这样我们就可以看到每一期精彩的杂志了. 仔细思考一下订杂志的过程,我们会发现这样几个特点:1.消费者订杂志不需要直接找出版社:2.出版社只需要把杂志交给邮局:3.邮局将杂志送达消费者.邮局在整个过程中扮演了非常重要的中转作用,在出版社和消费者相互不需要知道对方的情况下,邮局完成了杂志的投递. 二. 发布-订阅消息模

Vue发布-订阅者模式

1.vue响应原理: vue.js采用数据劫持结合发布-订阅者模式,通过Object.defineProperty()来劫持data中各个属性的setter.getter,在数据变动时,发布消息给订阅者,触发响应的监听回调. (setter和getter是对象的存储器属性,是一个函数,用来获取和设置值) 2.发布-订阅者模式的作用: 处理一对多的场景,应用于不同情况下的不同函数调用 优点:低耦合性,易于代码维护: 缺点:若订阅的消息未发生,需消耗一定的时间和内存. <!DOCTYPE html>

javascript 设计模式 -- 发布/订阅模式

直接上代码: index.html : <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>设计模式</title> </head> <body> <div id="box"> <div>{{message}}</div> <

学习javascript设计模式之发布-订阅(观察者)模式

1.发布-订阅模式又叫观察者模式,它定义对象之间一种一对多的依赖关系. 2.如何实现发布-订阅模式 2-1.首先指定好发布者 2-2.给发布者添加一个缓冲列表,用户存放回调函数以便通知订阅者 2-3.最后发布消息时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数 例子: var salesOffice = {};salesOffice.clientList = [];salesOffice.listen = function(key,fn){    if(!this.clientL

JS 设计模式八 -- 发布订阅者模式

概念 发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多(一个发布,多个观察)的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知. 优点 1.支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象. 2.发布者与订阅者耦合性降低 缺点 创建订阅者需要消耗一定的时间和内存. 如果过度使用的话,反而使代码不好理解及代码不好维护. 代码实现 var Event = (function(){ var list = {}, // 缓

[转] 浅析JavaScript设计模式——发布-订阅/观察者模式

前一段时间一直在写CSS3的文章 一直都没写设计模式 今天来写写大名鼎鼎观察者模式 先画张图 观察者模式的理解 我觉得还是发布-订阅模式的叫法更容易我们理解 (不过也有的书上认为它们是两种模式……) 这就类似我们在微信平台订阅了公众号 当它有新的文章发表后,就会推送给我们所有订阅的人 我们可以看到例子中这种模式的优点 我们作为订阅者不必每次都去查看这个公众号有没有新文章发布, 公众号作为发布者会在合适时间通知我们 我们与公众号之间不再强耦合在一起.公众号不关心谁订阅了它, 不管你是男是女还是宠物

ActiveMQ的(点对点&amp;发布/订阅通信模式)和(持久化方式)

ActiveMQ的持久化 消息持久性对于可靠消息传递来说应该是一种比较好的方法,有了消息持久化,即使发送者和接受者不是同时在线或者消息中心在发送者发送消息后宕机了,在消息中心重新启动后仍然可以将消息发送出去,如果把这种持久化和ReliableMessaging结合起来应该是很好的保证了消息的可靠传送. 消息持久性的原理很简单,就是在发送者将消息发送出去后,消息中心首先将消息存储到本地数据文件.内存数据库或者远程数据库等,然后试图将消息发送给接收者,发送成功则将消息从存储中删除,失败则继续尝试.消

设计模式发布订阅

参考 http://blog.csdn.net/calmreason/article/details/50895191 主要是发布者存储订阅者的指针 发布时依次NOTIFY 发送信息 订阅者在订阅时将自己加入到发布者的容器中 使用C++11 的智能指针 需要注意智能指针的 weak shared的转换 以及订阅者析构后的对应操作 代码 // MyDesignPattern.cpp: 定义控制台应用程序的入口点. // #include "stdafx.h" #include <i