【读书笔记】读《JavaScript设计模式》之代理模式

1、定义javascript

  代理是一个对象,它能够用来控制对另外一个对象的访问。它与另外那个对象实现了一样的接口,而且会把任何方法调用传递给那个对象。另外那个对象一般称为本体。代理能够代替其实体被实例化,并使其可被远程访问。它还能够把本体的实例化推迟到真正须要的时候,对于实例化比较费时的本体,或者因尺寸较大以致于不用时不易保存在内存中的本体,这特别有用。在处理那些须要较长时间才能把数据载入用户界面的类时,代理也大有裨益。html

  代理模式最基本的形式是对访问进行控制。代理对象和另外一个对象(本体)实现的是一样的接口。实际上工做仍是本体在作,它才是负责执行所分派的任务的那个对象或类。代理对象所作的不外乎节制对本体的访问。要注意,代理对象并不会在另外一对象的基础上添加方法或修改方法(就像装饰者那样),也不会简化那个对象的接口(就像门面元素那样)。它实现的接口与本体彻底相同,全部对它进行的方法调用都会被传递给本体。java

2、代理如何控制对本体的访问程序员

  2.1 直接代理web

var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']);

var PublicLibrary = function(books) { // implements Library
    this.catalog = {};
    for(var i = 0, len = books.length; i < len; i++) {
        this.catalog[books[i].getIsbn()] = { book: books[i], available: true };
    }
};
PublicLibrary.prototype = {
    findBooks: function(searchString) {
        var results = [];
        for(var isbn in this.catalog) {
            if(!this.catalog.hasOwnProperty(isbn)) continue;
            if(searchString.match(this.catalog[isbn].getTitle()) ||
                    searchString.match(this.catalog[isbn].getAuthor())) {
                results.push(this.catalog[isbn]);
            }
        }
        return results;
    },
    checkoutBook: function(book) {
        var isbn = book.getIsbn();
        if(this.catalog[isbn]) {
            if(this.catalog[isbn].available) {
                this.catalog[isbn].available = false;
                return this.catalog[isbn];
            }
            else {
                throw new Error('PublicLibrary: book ' + book.getTitle() +
                        ' is not currently available.');
            }
        }
        else {
            throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
        }
    },
    returnBook: function(book) {
        var isbn = book.getIsbn();
        if(this.catalog[isbn]) {
            this.catalog[isbn].available = true;
        }
        else {
            throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
        }
    }
};
var PublicLibraryProxy = function(catalog) { // implements Library
    this.library = new PublicLibrary(catalog);
};
PublicLibraryProxy.prototype = {
    findBooks: function(searchString) {
        return this.library.findBooks(searchString);
    },
    checkoutBook: function(book) {
        return this.library.checkoutBook(book);
    },
    returnBook: function(book) {
        return this.library.returnBook(book);
    }
};

  PublicLibraryProxy和PublicLibrary实现了一样的接口和同一批方法。这个类在实例化时会建立一个PublicLibrary实例并将其做为属性保存。若是调用该类的某个方法,它会经过这个属性在其PublicLibrary实例上调用同名方法。这种类型的代理也能够经过检查本体的接口并为每个方法建立对应方法这样一种方式动态地建立。设计模式

  2.2 虚拟代理模块化

var PublicLibraryVirtualProxy = function(catalog) { // implements Library
    this.library = null;
    this.catalog = catalog; // Store the argument to the constructor.
};
PublicLibraryVirtualProxy.prototype = {
    _initializeLibrary: function() {
        if(this.library === null) {
            this.library = new PublicLibrary(this.catalog);
        }
    },
    findBooks: function(searchString) {
        this._initializeLibrary();
        return this.library.findBooks(searchString);
    },
    checkoutBook: function(book) {
        this._initializeLibrary();
        return this.library.checkoutBook(book);
    },
    returnBook: function(book) {
        this._initializeLibrary();
        return this.library.returnBook(book);
    }
};

  PublicLibraryVirtualProxy会把构造函数的参数保存起来,直到有方法被调用时才真正执行本体的实例化。这样一来,若是图书馆对象一直没有被调用到,那么它就不会被建立出来。虚拟代理一般具备某种能触发本体的实例化的事件。在本例中,方法调用就是触发元素。函数

3、与装饰者模式的区别fetch

  共同点:二者都要对其余对象进行包装,都要事先与被包装对象相同的接口,并且都要把方法调用传递给被包装对象。
  不一样点:
  1> 装饰者会对被包装对象的功能进行修改或扩充,而代理只不过是控制对它的访问。除了有时可能会添加一些控制代码以外,代理并不会对传递给本体的方法调用进行修改。而装饰者就是为修改方法而生的。
  2> 在装饰者模式中,被包装对象的实例化过程是彻底独立的。这个对象建立出来以后,你能够随意为其包裹上一个或更多装饰者。而在代理模式中,被包装对象的实例化时代理的实例化过程的一部分。在某些类型的虚拟代理中,这种实例化受到严格控制,它必须在代理内部进行。
  3> 代理不会像装饰者那样相互包装。它们一次只使用一个。优化

4、使用场合

  虚拟代理是一个对象,用于控制对一个建立开销昂贵的资源的访问。虚拟代理是一种优化模式。若是有些对象须要使用大量内存保存其数据,而你并不须要在实例化完成以后访问这些数据,或者,其构造函数须要进行大量计算那就应该使用虚拟代理将设置开销的产生推迟到真正须要使用数据的时候。代理能够在设置的进行过程当中提供相似于“正在加载.....”这样的消息,这能够造成一个反应积极的用户界面,以避免让用户面对一个没有任何反馈的空白页面发呆,不知道究竟发生了什么事。

  远程代理则没有这样清楚的用例。若是须要访问某种远程资源的话,那么最好是用一个类或独享来包装它,而不是一遍又一遍地手工设置XMLHttpRequest对象。问题在于应该用什么类型的对象来包装这个资源呢?这主要是个命名问题。若是包装对象实现了远程资源的全部方法,那么它就是一个远程代理。若是它会在运行期间增添一些方法,那它就是一个装饰者。若是它简化了该远程资源(或多个远程资源)的接口,那它就是一个门面。远程代理是一种结构型模式,它提供了一个访问位于其余环境中的资源的原生JavaScript API。

  下面引入包装web服务的通用包装模式的代理——

var WebserviceProxy = function() {
    this.xhrHandler = XhrManager.createXhrHandler();
};
WebserviceProxy.prototype = {
    _xhrFailure: function(statusCode) {
        throw new Error('StatsProxy: Asynchronous request for stats failed.');
    },
    _fetchData: function(url, dataCallback, getVars) {
        var that = this;
        var callback = {
            success: function(responseText) {
                var obj = eval('(' + responseText + ')');
                dataCallback(obj);
            },
            failure: that._xhrFailure
        };

        var getVarArray = [];
        for(varName in getVars) {
            getVarArray.push(varName + '=' + getVars[varName]);
        }
        if(getVarArray.length > 0) {
            url = url + '?' + getVarArray.join('&');
        }

        xhrHandler.request('GET', url, callback);
    }
};

/* StatsProxy class, using WebserviceProxy. */

var StatsProxy = function() {}; // implements PageStats
extend(StatsProxy, WebserviceProxy);

/* Implement the needed methods. */

StatsProxy.prototype.getPageviews = function(callback, startDate, endDate, page) {
    this._fetchData('/stats/getPageviews/', callback, {
        'startDate': startDate,
        'endDate': endDate,
        'page': page
    });
};
StatsProxy.prototype.getUniques = function(callback, startDate, endDate, page) {
    this._fetchData('/stats/getUniques/', callback, {
        'startDate': startDate,
        'endDate': endDate,
        'page': page
    });
};
StatsProxy.prototype.getBrowserShare = function(callback, startDate, endDate, page) {
    this._fetchData('/stats/getBrowserShare/', callback, {
        'startDate': startDate,
        'endDate': endDate,
        'page': page
    });
};
StatsProxy.prototype.getTopSearchTerms = function(callback, startDate, endDate, page) {
    this._fetchData('/stats/getTopSearchTerms/', callback, {
        'startDate': startDate,
        'endDate': endDate,
        'page': page
    });
};
StatsProxy.prototype.getMostVisitedPages = function(callback, startDate,endDate) {
    this._fetchData('/stats/getMostVisitedPages/', callback, {
        'startDate': startDate,
        'endDate': endDate
    });
};

5、优点

  远程代理的好处在于,能够把远程资源当作本地JavaScript对象使用。它减小了为远程访问资源而不得不编写的粘合性代码的数量,而且为此提供了单一的接口。若是远程资源提供的API发生了改变,须要修改的代码只有一处。它还把与远程资源相关的全部数据统一保存在一个地方,其中包括资源的URL、数据格式、命令和相应的结构。若是须要访问多个WEB服务,那么能够先建立一个抽象的通用远程代理类,而后针对每一种要访问的web服务派生出一个子类。

  虚拟代理的好处在于:能够把大对象的实例化推迟到其余元素加载完毕以后。若是虚拟代理包装的资源没有被用到,那么根本就不会被加载。不用操心实例化开销的问题。

6、劣势

  代理能够掩盖了大量复杂行为。

  对于远程代理而言,其背后的复杂行为包括发出XHR请求、等待响应、对响应接口进行解析以及输出收到的数据。在使用远程代理的程序员眼里,它可能就像一个本地资源,但访问它花的时间却比访问本地资源要出几个数量级。并且,它须要和毁掉函数结合使用,由于让方法直接放回结果是行不通的,这给代码增长了必定的复杂性,而且进一步拆穿了其访问资源的假象。这里的问题也能经过精心编撰的程序文档来消除(至少也能够减轻其不利影响)。

  对于虚拟代理也是如此。它掩盖了推迟本体的实例化的逻辑。使用这种代理的程序员并不清楚又那些操做会触发对象的实例化。
  综上,由于代理与其本体彻底能够互换,若是没有使人信服的理由使用代理的话(或者下降代码的冗余程度,或者提升其模块化的程度,或运行效率),最好仍是选择直接访问本体这种简单得多的方法。

 

源自:JavaScript设计模式(人民邮电出版社)——第十二章,装饰者模式

相关文章
相关标签/搜索