2013年5月17日金曜日

design patterns – Proxy


「プロキシ」の語義は「代理」であり、この語義自体がプロキシパターンの役目を十分に説明しています。さて、一体何の代理をするのでしょうか。また、なぜオリジナルを使用しないのでしょうか。

プロキシを必要とする理由

基本的には、他のオブジェクトの代理をするオブジェクトを作成することになります。以下のような理由により、プロキシオブジェクトの作成が必要となります。
  1. 本当に必要になるまで、大きなオブジェクトのインスタンス化を遅らせる
  2. リモートのオリジナルオブジェクトにアクセスを提供する
  3. オリジナルオブジェクトに対するアクセスを制御する

仮想プロキシ

仮想プロキシは、上のリストの最初のケースに相当し、大きなオブジェクトのインスタンス化を遅らせます。仮に、コード内に巨大なオブジェクトがあるとします。ここでの巨大オブジェクトとは、長く複雑なロジックを持つ機能を多数備えたオブジェクトか、あるいは膨大なデータを持つオブジェクトです。仮想プロキシの使用が適しているのは、オブジェクトがインスタンス化されるが使用されないという確かな見通しがある場合、または、負荷の高い作業を直ちに実行すると、高速な処理と応答を行いたいときに処理が遅くなるので、本当に必要になるまでその作業を延期したい場合です。例えば、VehicleListというオブジェクトがあるとします。これがオリジナルのオブジェクトです。このオブジェクトは、インスタンス化されたときに、自動車ごとにT型フォード以来の自動車メーカーとモデルの長いリストが含められます。そして、明らかに、作成に高いコストがかかります。このコードは仮想プロキシを作成し、オブジェクトのインスタンス化を遅らせます。
var VehicleListProxy = function() { // Variable to hold real VehicleList when it's instatiated this.vehicleList = null; }; VehicleListProxy.prototype = { // Whenever a method requires that the real VehicleList is created // it should call this function to initialize it (if not already // initialized) _init: function() { if ( !this.vehicleList ) { this.vehicleList = new VehicleList(); } }, getCar: function( ... ) { this._init(); return this.vehicleList.getCar( ... ); }, ... }
このコードは、すべての要求をオリジナルオブジェクトに渡すオブジェクトを作成するだけですが、要求のいずれかが実行されるまでオリジナルオブジェクトのインスタンス化は行われません。必要なときにオリジナルオブジェクトが確実にインスタンス化されるように、他のすべてのメソッドによって呼び出されるメソッドを作成しました。

リモートプロキシ

リモートプロキシは、仮想プロキシと基本的に同じですが、オリジナルオブジェクトのインスタンス化を遅延させるのではなく、オリジナルオブジェクトはインターネット上のリモートの場所に既に存在しています。オブジェクトプロパティを通じて直接アクセスできるオリジナルオブジェクトに要求を単に渡すのではなく、AJAX要求として渡す必要があります。同じインターフェイスを採用するリモートオブジェクトがサーバーに存在していても問題ありません。オブジェクトが存在しているかどうかも問題になりません。そのURLへのAJAX要求が期待どおりに動作し、期待する値を返す限り、サーバー上に何が存在していてもかまいません。VehicleListProxyを使用している例をもう一度見てみましょう。
var VehicleListProxy = function() { this.url = "http://www.welistallcarmodels.com"; }; VehicleListProxy.prototype = { getCar: function( ... ) { // Skip the rest of the implementation to just show the // important stuff ajax( this.url + "/getCar/" + args); // Package up the data that you got back and return it }, ... }
シンプルそのものです。サーバーに処理を任せる場合と処理をローカルで行う場合を判定する明示的な関数を通して、Backbone.js内のモデルとコレクションは、ほとんどプロキシと同様に機能します。本記事で触れた「モデル」、「コレクション」、「Backbone.js」などの用語になじみがない場合は、Adobe Developer Connectionの「Christophe Coenraets' Backbone.js tutorials」をお読みいただくか、筆者が製作した「Backbone.jsビデオチュートリアルシリーズ」をご覧いただくと役に立つかもしれません。または、特に気にせず忘れていただいても大丈夫です。

プロキシ経由のアクセス制御

次は、プロキシのユースケースの最後となる「オリジナルオブジェクトに対するアクセス制御」をご紹介します。JavaScriptにおけるこの種のプロキシについて最初に理解しなければならないのは、実際にオブジェクトへのアクセスを制御するには、そのオブジェクトの周囲にクロージャが必要であるということです。クロージャがないと、グローバルスコープからアクセス可能になってしまいます。次に、必要とするすべての人に対して、確実にプロキシが利用可能になるようにします。この例では、計画されたリリース日までは、だれもアクセスできないようにします。どのようにこれを行ったか、以下のコードを見てみましょう。
// Close it off in a function (function() { // We already know what the VehicleList looks like, so I // won't rewrite it here var VehicleList = ... var VehicleListProxy = function() { // Don't initialize the VehicleList yet. this.vehicleList = null; this.date = new Date(); }; VehicleListProxy.prototype = { // this function is called any time any other // function gets called in order to initialize // the VehicleList only when needed. The VehicleList will // not be initialized if it isn't time to yet. _initIfTime: function() { if ( this._isTime() ) { if ( !this.vehicleList ) { this.vehicleList = new VehicleList(); } return true; } return false; }, // Check to see if we've reached the right date yet _isTime() { return this.date > plannedReleaseDate; }, getCar: function(...) { // if _initIfTime returns a falsey value, getCar will // return a falsey value, otherwise it will continue // and return the expression on the right side of the // && operator return this._initIfTime() && this.vehicleList.getCar(...); }, ... } // Make the VehicleListProxy publicly available window.VehicleListProxy = VehicleListProxy; // you could also do the below statement so people don't even // know they're using a proxy: window.VehicleList = VehicleListProxy; }());
上のコードでは、すべてのパブリックメソッドが、まず、アクセスできるかどうかを確認するためにthis.initIfTime()を呼び出し、次に、アクセスを許可された場合は、必ずvehicleListがインスタンス化されるようにします。initIfTime()falseを返した場合は、returnステートメント内の条件式がfalseを返し、その次の式は実行されません。しかし、メソッドがinitIfTimeからtrueを受け取った場合は、その次の式が実行され、値が返されます。
最終的に、最後の数行で、windowにアタッチすることによって、コードの他の部分に対してプロキシが公開されます。上で示したように、ユーザーがプロキシの存在を意識さえしなくても、プロキシ経由でのアクセス制御を行うことができるのです。
プロキシパターンについてもう少し詳しく知りたい場合は、筆者のJavaScriptブログで「JavaScript Design Patterns: Proxy」の記事をお読みください。

0 件のコメント: