委譲

委譲 (英: delegation) とはオブジェクト指向プログラミングにおいて、あるオブジェクトの操作を一部他のオブジェクトに代替させる手法のこと。 委譲を行うオブジェクトは委譲先オブジェクトへの参照を持ち、必要に応じてその参照を切り替える事で動作にバリエーションを持たせる事ができる。一種の実装遅延、プラグイン機構である。一例としては、オブジェクトの編集を行う時、編集の前処理、後処理を本処理と独立させ委譲先に任せる事で、オブジェクト本体の変更を最小限にとどめ局所性を向上させる、などがある。 操作の代替という観点では他に代理 (Proxy) と呼ばれる手法があるが、この場合は代理側のオブジェクトが実体への参照を保持する事で操作のフィルタを行う概念であり、実装の分離を目的とする委譲とは異なる。 委譲を引き受けるオブジェクトはどのような操作を実装しなければならないか知っている必要があるため、インタフェースと併用される場合が多い。 いくつかの言語ではデザインパターンにおける手法であり、いくつかの言語ではコードで実装されライブラリとして提供されている。 #include <iostream> struct ExampleInterface { virtual void Print() = 0; }; class Something1 : public ExampleInterface { public: void Print() { std::cout << "Something1::Print() is called." << std::endl; } }; class Something2 : public ExampleInterface { public: void Print() { std::cout << "Something2::Print() is called." << std::endl; } }; class SomethingDelegator { ExampleInterface *implementer; public: SomethingDelegator() : implementer() { } void setImplementer(ExampleInterface *something) { implementer = something; } void ExecuteProcess() { // 処理の詳細をExampleInterface::Print関数の実装者に委譲している if (implementer) { implementer->Print(); } } }; int main() { SomethingDelegator delegator; Something1 source1; delegator.setImplementer(&source1); // Something1::Print関数が呼ばれる delegator.ExecuteProcess(); Something2 source2; delegator.setImplementer(&source2); // Something2::Print関数が呼ばれる delegator.ExecuteProcess(); return 0; } class A { public void foo() { System.out.println("A.foo() is called."); } } class B { private A a = new A(); public void bar() { a.foo(); } } SelfやJavaScriptといったプロトタイプベースの言語では、言語機能として委譲と継承の中間とも言えるプロトタイプ機能を備えており、委譲先のオブジェクトを書き換えることはできないものの手軽に委譲ができるようになっている。 プロトタイプを使った委譲の例： /* 共通の反復処理を提供するオブジェクトとしてIterableを定義。 // Iterableはprototype(委譲先)の要件としてdoEachを要求する。 this.doEach = function( aBlock ) { // subclassResponsibility; } */ Iterable = function() { this.copyEmpty = function() { return []; } // 委譲先に追加する要素の選択関数 this.select = function( aBlock ) { var result = this.copyEmpty(); this.doEach ( function( anEach ) { if( aBlock( anEach ) ) result.push( anEach ); } ); return result; } } BaseCollection = [ 0, 1, 2, 3, 4 ]; BaseCollection.doEach = function( aBlock ) { for( var i = 0; i < this.length && false !== aBlock( this[ i ] ); ++i ) ; } // 配列にselect( aBlock )を追加する。 Iterable.prototype = BaseCollection; Collection = new Iterable().select( function( aEach ){ return 3 < aEach } ); BaseCollection = { 0: 4, 1: 3, 2: 2, 3: 1, 4: 0 }; BaseCollection.doEach = function( aBlock ) { for( var i in this ) { if( false === aBlock( this[ i ] ) ) return; } } // 連想配列にselect( aBlock )を追加する。 Iterable.prototype = BaseCollection; Collection = new Iterable().select( function( aEach ){ return 3 < aEach } ); なお、Selfの様に純粋なプロトタイプベースの言語ではprototype変数やnewに相当するものは無い。単純にプロトタイプとなるオブジェクトを委譲先としてオブジェクトを生成する機能のみ備わっているため、JavaScriptの様に委譲元を再利用するにはメソッド等サブルーチンの追加が必要となる。 Objective-Cの例を説明する。Objective-Cではデリゲート先のオブジェクトはどんなオブジェクトでも構わない。デリゲートしないときはnilを指定してもよい。Objective-CはC++やJavaとは異なり実行時解決を採用しているので、デリゲート先に指定したオブジェクトが必要なメソッドを持っていなくてもコンパイルエラーにはならず、単に何も実行されないだけである。文法上で特殊な扱いはされておらず、平時の記述の枠内で処理される典型的な実装パターンとして用いられる。 あるオブジェクトAがメインウィンドウを持っていて、そのAにメインウィンドウの処理を委譲する場合 [mainWindow setDelegate: self]; selfは自分自身を指すポインタである。[ ]内はC言語に対してObjective-Cで拡張された文法で、インスタンス変数mainWindowに対してsetDelegateメッセージを送っている。 このdelegateを設定することで、Aがメインウィンドウの処理、たとえばウィンドウを閉じる前に何かしたい場合に用いるメソッドwindowWillCloseをかわりに実行するようになる。Aに -(void)windowWillClose:(NSNotification*)notification { // 処理 } のようなメソッドを用意しておけば、ウィンドウのクラス(NSWindow)に修正を加えたり、NSWindowを継承したクラスを作らなくても振る舞いをカスタマイズすることができる。 Smalltalkにおける委譲も基本的には他の言語と同じである。 ただし、メッセージ機構を利用することでSmalltalk独特の委譲を行うことが出来る。 Smalltalkは、オブジェクトに対しメッセージが送られた際、メッセージ内のセレクターに該当するメソッドがオブジェクト内に無ければ、そのメッセージはdoesNotUnderstand:メソッドに送られる。この時doesNotUnderstand:メソッドで受け取ったメッセージは自由に他のオブジェクトに送りつけることが出来る。 なお、Objective-CにもdoesNotUnderstand:と同等の仕組みが存在し、同様の処理を記述できる。上記のObjective-Cの委譲もこのメッセージ処理の仕組みを利用している。 委譲用クラスの定義例： "他のオブジェクトに委譲するクラスの登録" Object subclass: #MouseDelegator "ObjectクラスからDelegatorクラスを派生させる。" instanceVariableNames: 'deletage filter' "委譲先のフィールド( メンバー変数 )を定義" classVariableNames: '' poolDictionaries: '' category: 'ExampleDelegate'. MouseDelegator methodsFor: 'accessing for delegate' ! deletage ^deletage ! deletage: anObject deletage := anObject. !! MouseDelegator methodsFor: 'accessing for filter' ! filter