【読書ノート】『JavaScriptデザインモード』を読むエージェントモード

17610 ワード

一、定義
エージェントは、別のオブジェクトへのアクセスを制御するオブジェクトです.他のオブジェクトと同じインタフェースを実装し、任意のメソッド呼び出しをそのオブジェクトに渡します.またそのオブジェクトは通常本体と呼ばれます.エージェントは、実体の代わりにインスタンス化され、リモートアクセスが可能になります.また、本体のインスタンス化を本当に必要なときに遅らせることもでき、インスタンス化に時間がかかる本体や、サイズが大きいため、使用しないときにメモリに保存しにくい本体に役立ちます.エージェントは、ユーザーインタフェースにデータをロードするのに時間がかかるクラスを処理する場合にも役立ちます.
エージェント・モードの最も基本的な形式は、アクセスを制御することです.エージェントオブジェクトと別のオブジェクト(ボリューム)は同じインタフェースを実現する.実際に仕事をしているのか、それとも本体がやっているのか、それこそ割り当てられたタスクを実行するオブジェクトやクラスです.エージェントオブジェクトが行うことは,ボリュームへのアクセスを節制することにほかならない.エージェントオブジェクトは、別のオブジェクトにメソッドを追加したり修正したりすることはなく、そのオブジェクトのインタフェース(フェース要素のように)を簡略化することはありません.実装されるインタフェースはボリュームと完全に同じであり、そのメソッド呼び出しはすべてボリュームに渡されます.
二、エージェントがどのように本体へのアクセスを制御するか
2.1ダイレクトエージェント
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は、メソッドが呼び出されるまで、ボリュームのインスタンス化を実行するコンストラクション関数のパラメータを保存します.これにより、図書館オブジェクトが呼び出されない限り、作成されません.仮想エージェントには、通常、ボリュームをトリガできるインスタンス化されたイベントがあります.この例では、メソッド呼び出しはトリガ要素です.
三、装飾者モードとの違い
共通点:両方とも他のオブジェクトを包装し,事前に被包装オブジェクトと同じインタフェースを用い,メソッド呼び出しを被包装オブジェクトに渡す.異なる点:1>装飾者は、パッケージされたオブジェクトの機能を変更または拡張しますが、エージェントはアクセスを制御するにすぎません.制御コードが追加される場合があるほか、エージェントはボリュームに渡されるメソッド呼び出しを変更しません.装飾者は方法を修正するために生まれた.2>装飾者モードでは、被包装対象のインスタンス化プロセスは完全に独立している.このオブジェクトが作成されると、1人以上の装飾者を自由に包むことができます.一方、エージェントモードでは、パッケージされたオブジェクトのインスタンス化時のエージェントのインスタンス化プロセスの一部である.一部のタイプの仮想エージェントでは、このインスタンス化は厳格に制御され、エージェント内で行わなければなりません.3>エージェントは装飾者のように互いに包装しない.一度に1つしか使用されません.
四、使用場面
仮想エージェントは、作成コストの高いリソースへのアクセスを制御するオブジェクトです.仮想エージェントは最適化モードです.インスタンス化が完了した後にデータにアクセスする必要がなく、大量のメモリを使用してデータを保存するオブジェクトがある場合、または構造関数を大量に計算する必要がある場合は、仮想エージェントを使用して、設定オーバーヘッドの発生を実際にデータを使用する必要がある場合に延期する必要があります.エージェントは、設定の進行中に「ロード中...」と同様に使用できます.このようなメッセージは、フィードバックのない空白のページに直面してぼんやりしていて、何が起こったのか分からないように、積極的なユーザーインタフェースを形成することができる.
リモートエージェントには、このような明確な例はありません.リモート・リソースにアクセスする必要がある場合は、XMLHttpRequestオブジェクトを何度も手動で設定するのではなく、クラスまたは独占でパッケージすることが望ましい.問題は、このリソースをどのようなタイプのオブジェクトで包装すべきかということです.これは主にネーミングの問題です.パッケージ・オブジェクトがリモート・リソースのすべての方法を実装している場合は、リモート・エージェントです.実行中にいくつかの方法を追加する場合は、装飾者です.リモート・リソース(または複数のリモート・リソース)のインタフェースを簡略化した場合、それは1つのフェースです.リモートエージェントは、他の環境にあるリソースにアクセスするオリジナルのJavaScript APIを提供する構造型モードです.
次に、ウェブサービスをパッケージする汎用パッケージモデルのエージェントを導入します.
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

    });

};

五、優勢
リモートエージェントの利点は、リモートリソースをローカルJavaScriptオブジェクトとして使用できることです.リモート・アクセスのために作成しなければならない接着コードの数を減らし、単一のインタフェースを提供します.リモートリソースが提供するAPIが変更された場合、変更するコードは1つしかありません.また、リモート・リソースに関連するすべてのデータを、リソースのURL、データ・フォーマット、コマンド、および対応する構造を含む場所に統一的に保存します.複数のWEBサービスにアクセスする必要がある場合は、まず抽象的な汎用リモートエージェントクラスを作成し、アクセスするウェブサービスごとにサブクラスを派生させることができます.
仮想エージェントの利点は、大きなオブジェクトのインスタンス化を他の要素のロードが完了した後に遅らせることです.仮想エージェントパッケージのリソースが使用されていない場合は、ロードされません.実例化費用の問題を心配する必要はありません.
六、劣勢
エージェントは多くの複雑な行為を隠すことができます.
リモートエージェントの場合、その背後にある複雑な動作には、XHRリクエストの発行、応答待ち、応答インタフェースの解析、および受信したデータの出力が含まれます.リモートエージェントを使用するプログラマーは、ローカルリソースのように見えるかもしれませんが、ローカルリソースにアクセスするよりも数桁の時間がかかります.さらに,メソッドを直接結果に戻すことは不可能であるため,コードに一定の複雑さを増大させ,アクセスリソースの仮象をさらに解体するために破壊関数と組み合わせて使用する必要がある.ここでの問題は,丹念に作成されたプログラムドキュメントによっても解消できる(少なくとも不利な影響を軽減できる).
仮想エージェントについても同様です.ボリュームのインスタンス化を遅らせる論理を隠しています.このエージェントを使用するプログラマは、オブジェクトのインスタンス化をトリガーする操作を知らない.以上、エージェントとそのボリュームは完全に互換性があるので、納得できる理由がなければエージェントを使用する(またはコードの冗長性を低減したり、モジュール化の程度を高めたり、実行効率を高めたり)と、ボリュームに直接アクセスするというよりずっと簡単な方法を選択したほうがよい.
 
出典:JavaScriptデザインモデル(人民郵便出版社)-第12章、装飾者モード