【译】观察者模式

观察者是一种设计模式,其中对象(被称为主体)根据它(观察者)维护对象的列表,在他们的状态发生任何改变会自动通知观察者。当主体需要通知观察者有关发生的事情时,它向观察者广播通知(其可以包括与通知主题相关的特定数据)。当我们不再希望特定观察者被通知他们注册的主题的改变时,主体可以将它们从观察者列表中删除。

#观察者与发布/订阅模式之间的差异 虽然Observer模式是有用的,需要注意,通常在JavaScript世界中,我们会发现它通常使用一个变体称为发布/订阅模式实现。虽然非常相似,这些模式之间有差异值得注意。

Observer模式要求希望接收主体通知的观察者(或对象)必须将此兴趣订阅到触发事件的对象(主体)。

然而,发布/订阅模式使用位于希望接收通知(订阅者)的对象和发起事件的对象(发布者)之间的主体/事件通道。该事件系统允许代码定义应用程序特定事件,其可以传递包含订户所需的值的自定义参数。这里的想法是避免订阅者和发布者之间的依赖。

这与观察者模式不同,因为它允许任何实现适当的事件处理程序的用户注册并接收由发布者广播的主题通知。

##优点 观察者和发布/订阅模式鼓励我们认真思考我们应用程序的不同部分之间的关​​系。它们还帮助我们识别包含直接关系的层,而不是用主体和观察者的集合来替换。这有效地可以用于将应用分解成更小,更松散耦合的块,以改进代码管理和重用的潜力。

使用Observer模式的另一个动机是我们需要保持相关对象之间的一致性,而不使类紧密耦合。例如,当对象需要能够通知其他对象而不对这些对象做出假设时。

当使用任一模式时,观察者和主体之间可能存在动态关系。这提供了大量的灵活性,当我们的应用的不同部分紧密耦合时,这可能不容易实现。

虽然它并不总是每个问题的最佳解决方案,但这些模式仍然是设计解耦系统的最佳工具之一,应该被任何JavaScript开发人员视为重要的实用工具。 ##缺点

其实,这些模式的一些问题实际上源于它们的主要益处。 在发布/订阅中,通过将发布者与订阅者分离,有时可能难以获得对我们的应用程序的特定部分如我们期望的功能的保证。

例如,发布者可以假设一个或多个订阅者正在监听它们。 假设我们使用这样的假设来记录或输出关于某些应用程序进程的错误。 如果执行记录崩溃(或由于某些原因失败)的订户,由于系统的解耦特性,发布者将没有办法看到这种情况。

模式的另一个缺点是用户对彼此的存在相当无知,并且忽视了切换发布者的成本。 由于订阅者和发布者之间的动态关系,依赖的更新可能难以跟踪。

#发布/订阅的实现 发布/订阅非常适合JavaScript生态系统,主要是因为在核心,ECMAScript实现是事件驱动。 这在浏览器环境中尤其如此,因为DOM使用事件作为其主要的交互API来编写脚本。

也就是说,ECMAScript和DOM都不提供用于在实现代码中创建自定义事件系统的核心对象或方法(除了DOM3 CustomEvent,它绑定到DOM,因此不是通用的)。

幸运的是,流行的JavaScript库如jQuery(自定义事件)已经有了一些实用程序,可以帮助轻松实现一个Publish / Subscribe系统。 下面我们可以看到一些例子:

// 发布
// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]);
$( el ).trigger( "/login", [{username:"test", userData:"test"}] );

// 订阅
// jQuery: $(obj).on( "channel", [data], fn );
$( el ).on( "/login", function( event ){...} );

// 取消订阅
// jQuery: $(obj).off( "channel" );
$( el ).off( "/login" );

注:Zepto的trigger方法仅支持在dom元素上触发事件。

#一个发布/订阅的实现

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 ));

原文地址:https://addyosmani.com/resources/essentialjsdesignpatterns/book/#observerpatternjavascript

Licensed under CC BY-NC-SA 4.0