SVC中观察者模式的应用

一、SVC中观察者模式实现原理

SVC (Subject-View-Controller)是目标——视图——控制器模型框架,应用于高度交互式的,低延迟的 web 应用程序。

原理:观察者模式定义了一种一对多的依赖关系,当一个对象改变时,其它所有的依赖对象自动发生改变。SVC 中的 Subject其实就是观察者模式中的被观察者,Subject的状态变化, 都会引发View 层次的变动,而SVC的View层次自然就对应观察者模式中的观察者。所以使用观察者模式可以使svc中的Subject和View得以分离。

 

引用的GitHub:https://github.com/joelzimmer/SVC/blob/master/svc.js

1.Subject

Subject:负责存储和处理数据。包含增加,删除和通知观察者的方法,当模型中的数据改变时,通知相应视图(观察者)做出改变。

由三部分组成

(1)Subject接口:实现简单的质量测试,通知视图,订阅/取消订阅功能

svc.Subject = Class.create({
    // Our constructor just sets up the mapping from notifcations to functions.
    initialize: function (args) {
        this._notificationToObservers = $H();
    },

    // Equality test is just a strict equals against another `subject` at this point. 
    isEqual: function (subject) {
        return this === subject;
    },

    // Destroy self. Notify that we are dead, and clear out all subscribed functions.
    destroy: function () {
        this.notify('subject:destroy');
        this._notificationToObservers = $H();
    },

    // Notify that a particular `notification` happened. The first variable passed along will be the subject.
    notify: function (notification) {
        var observers = this._notificationToObservers.get(notification);
        var args = $A(arguments);
        
        // Remove the notification from the arguments array.
        args.shift();
        
        // Add the subject as the first argument.
        args.unshift(this);

        if (observers) {
            observers.invoke('apply', null, args);
        }
    },

    // Add a subscription of a `f`unction call for a particular `notification`.
    subscribe: function (notification, f) {
        var observers = this._notificationToObservers.get(notification);

        if (observers) {
            observers.push(f);
        } else {
            this._notificationToObservers.set(notification, [f]);
        }
    },

    // Remove a subscription of a `f`unction call for a particular `notification`.
    unsubscribe: function (notification, f) {
        var observers = this._notificationToObservers.get(notification);

        if (observers) {
            this._notificationToObservers.set(notification, observers.without(f));
        }
    }
});

(2)ModifiableSubject接口:是Subject接口的详细补充,实现设置属性,保存对象状态信息等功能

svc.ModifiableSubject = Class.create(svc.Subject, {
    // Create the object, setting the dirty flag to false.
    initialize: function ($super, args) {
        $super(args);
        
        this._dirty = false;
        
        // TODO: automatically do silent sets for all values in args that aren't reserved
    },

    // Retrieve the particular value of a `property`.
    get: function (property) {
        return this[':' + property];
    },

    // Get all the `properties` for a ModifiableSubject.
    properties: function () {
        var properties = $A();
        for (var property in this) {
            if (property.charAt(0) === ':') {
                properties.push(property.slice(1));
            }
        }
        return properties;
    },

    // Set the object to be `clean` again. Notifies `subject:clean`.
    clean: function () {
        this._dirty = false;
        this.notify('subject:clean');
    },

    // Set the object to be `dirty`. Notifies `subject:dirty`.
    dirty: function () {
        this._dirty = true;
        this.notify('subject:dirty');
    },

    // Set a `property` to a particular `value`. If the `silent` flag is set, then no notification will be
    // made. If it isn't set (or is not included), the notification will be `subject:change:<property>`.
    // Returns true if the value is changed.
    set: function (property, value, silent) {
        if (this.get(property) == value) { return false; }
        this[':' + property] = value;
        if (! silent) {
            this.notify('subject:change:' + property);
            this.dirty();
        }
        return true;
    },

    // Checks to see if the object has changes.
    isDirty: function () {
        return this._dirty;
    }
});

(3)Collection接口:是Subject的组成部分,用来存储所有与之相关的被观察者数据,并维护数据。

  

svc.Collection = Class.create(svc.Subject, {
        // Our constructor expects one variable labeled `collection`, which can
        // be an array of subjects. It also can take a `sortFunction` that can be used to sort the collection
    initialize: function ($super, args) {
        $super(args);
        this._collection = $A(args.collection || []);
        this._sortFunction = args.sortFunction;
        if (this._sortFunction) {
            this._collection.sort(this._sortFunction);
        }
    },
    
    // Get a value in the `Collection` from a particular `index`. Will throw errors for bad indices.
    at: function (index) {
        if (! this.inRange(index)) {
            throw 'svc.Collection.at: invalid index.';
        }
        return this._collection[index];
    },

    // Get a particular `subject` from the `collection`. Uses the `subject`s isEqual property to get the object,
    // returns `null` if nothing is found.
    get: function (subject) {
        return this._collection.find(
            function (entry) {
                return entry.isEqual(subject);
            }
        );
    },

    // Return all items in the collection.
    getAll: function () {
        return this._collection;
    },

    // Determines where a `subject` is in the `collection`. Uses the `subject`s isEqual property to get the object,
    // and will return -1 if not found.
    indexOf: function (subject) {
        var index = -1;
        this._collection.find(
            function (entry) {
                ++index;
                return entry.isEqual(subject);
            }
        );

        return index === this.size() ? -1 : index;
    },

    // Determines whether or not an index fits into the `collection`.
    inRange: function (index) {
        return index >= 0 && index < this.size();
    },

    // Returns the size of the collection.
    size: function () {
        return this._collection.length;
    },

    // Add a `subject` to the `collection` if it isn't already in the `collection`.
    add: function (subject) {
        if (this.get(subject)) { return; }
        this._collection.push(subject);
        if (this._sortFunction) {
            this._collection.sort(this._sortFunction);
        }
        this.notify('collection:add', subject);
        subject.notify('collection:add');
    },

    // Remove all `subject`s from the `collection`.
    clear: function () {
        var cleared = this.getAll();
        this._collection = $A();
        cleared.invoke('notify', 'collection:clear');
        this.notify('collection:clear');
    },

    // Remove a single `subject` from the `collection`.
    remove: function (subject) {
        var entry = this.get(subject);
        if (! entry) { return; }
        this._collection = this._collection.without(entry);
        entry.notify('collection:remove');
        this.notify('collection:remove', entry);
        return entry;
    },

    _each: function (iterator) {
        this._collection._each(iterator);
    }
});

// Mixin `Enumerable` functionality.
svc.Collection.addMethods(Enumerable);

2. View

View:负责数据的可视化表示,接受Subject的通知后更新视图(体现在订阅/取消订阅)。

(1)View接口:完成以上基本功能

svc.View = Class.create({
    // Our constructor calls draw and subscribes a teardown method to the destroy event. It requires a 
    // `subject` variable which will be the `Subject` the view subscribes to.
    initialize: function (args) {
        this._subject = args.subject;
        this._element = this.draw();

        this._subscribedFunctions = $H();
        this.subscribe('subject:destroy', this.tearDown.bind(this));
    },
    
    // This will be where you define the main element of a view, you can also define other elements
    // which can be stored as instance variables to reduce page wide scans.
    draw: function () {
        throw "View.js: draw must be defined in a subclass.";
    },
    
    // This will need to go, but is required for now.
    update: function () {
        // stub method, needs to be impemented in subclasses as well.
    },
    
    // The default method that gets called when the subject is destroyed. It removes the element
    // from the page and unsubscribes all events. Feel free to override it as needed.
    tearDown: function () {
        var element = this.getElement();
        if (element) {
            element.hide();
            var parentElement = element.parentNode;
            if (parentElement) { parentElement.removeChild(element); }
        }
        
        this.unsubscribeAll();
    },
    
    // Gets the main `element` of the view.
    getElement: function () {
        return this._element;
    },
    
    // Get the `subject` the view is watching. 
    getSubject: function () {
        return this._subject;
    },
    
    // Subscribe a `function` to a particular `notification` issued by the subject.
    subscribe: function (notification, fn) {
        this._subscribedFunctions.set(notification, fn);
        this.getSubject() && this.getSubject().subscribe(notification, fn);
    },
    
    // Unsubscribe from a particular `notification` issued by the `subject`.
    unsubscribe: function (notification) {
        var fn = this._subscribedFunctions.get(notification);
        this._subscribedFunctions.unset(notification);
        this.getSubject() && this.getSubject().unsubscribe(notification, fn);
    },
    
    // Clear out all subscribed functions for the view.
    unsubscribeAll: function () {
        this._subscribedFunctions.keys().each(this.unsubscribe.bind(this));
    }
});

(2)ActionView接口:View接口的补充,根据用户输入修改视图

svc.ActionView = Class.create(svc.View, {
        
    // Our initializer takes an `action`, `controller`, and `events` along with the variables a `View` needs. The 
    // `controller` is a `Controller` object and the `action` is the name of the function you wish to call
    // when the user completes the required action. Users define `events` that the view will respond to.
    initialize: function ($super, args) {
        
        this._action = args.action;
        this._controller = args.controller;
        
        // `field` gets set in draw()
        this._field = null;

        $super(args);

        // We store the fire function so it can be unregistered later.
        this._boundFireFunction = this.fire.bind(this);

        this.observeDOMEvents(args.events);
    },

    // Get the `ActionView`s `field`.
    getField: function () {
        return this._field;
    },
    
    // We associate the passed in `events` with the view so that the view will call out to the controller
    // when it's interacted with.
    observeDOMEvents: function (events) {
        // We don't operate on `ActionView`s without fields.
        if (! this.getField()) {
            return;
        }

        if (!Object.isArray(events)) {
            events = [events];
        }

        events.uniq().compact().each(
            function (event) {
                this.getField().observe(event, this._boundFireFunction);
            }.bind(this)
        );
    },

    // We call the desired `action` in the `controller` and include the `subject` as the first variable. 
    fire: function () {
        var args = $A(arguments);
        args.unshift(this.getSubject());
        this._controller[this._action].apply(this._controller, args);
    }
});

 

3.Controller

Controller:位于视图和模型中间,负责接受用户的输入,将输入进行解析并反馈给Subject。

(1)AjaxController接口:完成以上基本功能。

svc.AjaxController = Class.create(svc.Controller, {
        
    // Our initializer takes an `actionMethod` and an `actionPath` in order to define how
    // we communicate with the server and where we talk to.
    initialize: function ($super, args) {
        $super(args);
        this._actionPath   = args.actionPath;
        this._actionMethod = args.actionMethod || 'post';
    },

    // Make a request to the `path` on the server, passing whatever `args` are included as parameters.
    // We're adding a `callback` here to be called `onComplete`, but we should really rethink this.
    makeRequest: function (args, callback) {
        var boundOnSuccess = this.onSuccess.bind(this);
        var wrappedSuccess = callback && typeof(callback) === 'function' ? boundOnSuccess.wrap(callback).bind(this) : boundOnSuccess;
        var req = new Ajax.Request(this.path(), {
            method:     this.method(),
            parameters: this.parameters().update(args),
            onCreate:   this.onCreate.bind(this),
            onSuccess:  wrappedSuccess,
            onFailure:  this.onFailure.bind(this),
            onComplete: this.onComplete.bind(this)
        });
    },

    // Defines the `method` of the request (usually `POST` or `GET`).
    method: function () {
        if (! this._actionMethod) { throw "AjaxController.js: method must be defined"; }
        return this._actionMethod;
    },

    // The method called when the AJAX request is completed.
    onComplete: Prototype.emptyFunction,
    
    // The method called when the AJAX request is created.
    onCreate:   Prototype.emptyFunction,
    
    // The method called when the AJAX request fails.
    onFailure:  Prototype.emptyFunction,
    
    // The method called when the AJAX request succeeds.
    onSuccess:  Prototype.emptyFunction,
    
    // An object representing the parameters, this should be extended or overwritten.
    parameters: function () { return $H(); },

    // Retrieves the `actionPath` of the request.
    path: function () {
        if (! this._actionPath) { throw "AjaxController.js: path must be defined"; }
        return this._actionPath;
    }
});

(2)AjaxSingleRequestController接口:AjaxController接口的补充,实现互斥访问功能

// AjaxSingleRequestController
// -------------

// This creates a simple mutex lock on AJAX queries so that only one happens at a time. It inherits from
// the normal `AjaxController`
svc.AjaxSingleRequestController = Class.create(svc.AjaxController, {
    // Our initializer is the same as `AjaxController`.
    initialize: function ($super, args) {
        $super(args);
        this._inProgress = false;
    },
    
    // Make a request if nothing is in progress.
    makeRequest: function ($super, args, callback) {
        if (this._inProgress) { return; }
        this._inProgress = true;
        $super(args, callback);
    },

    // When the request finishes, unlock the progress lock
    onComplete: function () { this._inProgress = false; }
});

 

二、给项目带来的好处

  • 一个模型提供不同的多个视图表现形式,也能够为一个模型创建新的视图而无须重写模型。一旦模型的数据发生变化,模型将通知有关的视图,每个视图相应地刷新自己。
  • 模型可复用。因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。
  • 提高开发效率。在开发界面显示部分时,你仅仅需要考虑的是如何布局一个好的用户界面;开发模型时,你仅仅要考虑的是业务逻辑和数据维护,这样能使开发者专注于某一方面的开发,提高开发效率。

转载于:https://www.cnblogs.com/betterming/p/9840651.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值