委譲

委譲  とはオブジェクト指向プログラミングにおいて、あるオブジェクトの操作を一部他のオブジェクトに代替させる手法のこと。

1. 概要
-----

委譲を行うオブジェクトは委譲先オブジェクトへの参照を持ち、必要に応じてその参照を切り替える事で動作にバリエーションを持たせる事ができる。一種の実装遅延、プラグイン機構である。一例としては、オブジェクトの編集を行う時、編集の前処理、後処理を本処理と独立させ委譲先に任せる事で、オブジェクト本体の変更を最小限にとどめ局所性を向上させる、などがある。

操作の代替という観点では他に代理  と呼ばれる手法があるが、この場合は代理側のオブジェクトが実体への参照を保持する事で操作のフィルタを行う概念であり、実装の分離を目的とする委譲とは異なる。

委譲を引き受けるオブジェクトはどのような操作を実装しなければならないか知っている必要があるため、インタフェースと併用される場合が多い。

いくつかの言語ではデザインパターンにおける手法であり、いくつかの言語ではコードで実装されライブラリとして提供されている。

2. 例
----

2.1. C++
--------

include 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 { implementer = something; }

void ExecuteProcess { // 処理の詳細をExampleInterface::Print関数の実装者に委譲している if  { implementer->Print; } } };

int main { SomethingDelegator delegator;

Something1 source1; delegator.setImplementer; // Something1::Print関数が呼ばれる delegator.ExecuteProcess;

Something2 source2; delegator.setImplementer; // Something2::Print関数が呼ばれる delegator.ExecuteProcess;

return 0; }<WtXmlEndTag />

2.2. Java
---------

class A { public void foo { System.out.println is called."); } }

class B { private A a = new A; public void bar { a.foo; } }<WtXmlEndTag />

2.3. JavaScript
---------------

SelfやJavaScriptといったプロトタイプベースの言語では、言語機能として委譲と継承の中間とも言えるプロトタイプ機能を備えており、委譲先のオブジェクトを書き換えることはできないものの手軽に委譲ができるようになっている。

プロトタイプを使った委譲の例： /* 共通の反復処理を提供するオブジェクトとしてIterableを定義。

// Iterableはprototypeの要件としてdoEachを要求する。 this.doEach = function { // subclassResponsibility; }

/Iterable = function { this.copyEmpty = function { return []; }

// 委譲先に追加する要素の選択関数 this.select = function { var result = this.copyEmpty;

this.doEach  { if ) result.push; } );

return result; } }

BaseCollection = [ 0, 1, 2, 3, 4 ]; BaseCollection.doEach = function { for; ++i ) ; } // 配列にselectを追加する。 Iterable.prototype = BaseCollection; Collection = new Iterable.select{ return 3 < aEach } );

BaseCollection = { 0: 4, 1: 3, 2: 2, 3: 1, 4: 0 }; BaseCollection.doEach = function { for { if ) return; } } // 連想配列にselectを追加する。 Iterable.prototype = BaseCollection; Collection = new Iterable.select{ return 3 < aEach } );

<WtXmlEndTag /> なお、Selfの様に純粋なプロトタイプベースの言語ではprototype変数やnewに相当するものは無い。単純にプロトタイプとなるオブジェクトを委譲先としてオブジェクトを生成する機能のみ備わっているため、JavaScriptの様に委譲元を再利用するにはメソッド等サブルーチンの追加が必要となる。

2.4. Objective-C
----------------

Objective-Cの例を説明する。Objective-Cではデリゲート先のオブジェクトはどんなオブジェクトでも構わない。デリゲートしないときはnilを指定してもよい。Objective-CはC++やJavaとは異なり実行時解決を採用しているので、デリゲート先に指定したオブジェクトが必要なメソッドを持っていなくてもコンパイルエラーにはならず、単に何も実行されないだけである。文法上で特殊な扱いはされておらず、平時の記述の枠内で処理される典型的な実装パターンとして用いられる。

あるオブジェクトAがメインウィンドウを持っていて、そのAにメインウィンドウの処理を委譲する場合 [mainWindow setDelegate: self];selfは自分自身を指すポインタである。[ ]内はC言語に対してObjective-Cで拡張された文法で、インスタンス変数mainWindowに対してsetDelegateメッセージを送っている。

このdelegateを設定することで、Aがメインウィンドウの処理、たとえばウィンドウを閉じる前に何かしたい場合に用いるメソッドwindowWillCloseをかわりに実行するようになる。Aに -windowWillClose:notification { // 処理 } のようなメソッドを用意しておけば、ウィンドウのクラスに修正を加えたり、NSWindowを継承したクラスを作らなくても振る舞いをカスタマイズすることができる。

2.5. Smalltalk
--------------

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 ^ filter. ! filter: anObject filter := anObject. !!

MouseDelegator methodsFor: 'event handling' ! click "clickメッセージをaTargetに委譲する" self filter click. !!

MouseDelegator methodsFor: 'message fowarding' ! doesNotUnderstand: aMessage "click以外のメッセージは全てdelegateに委譲する。" aMessage sendTo: self delegate. !!

MouseDelegator class methodsFor: 'instance creation' ! withDelegate: aDelegateObject withFilter: aFilterObject

"MouseDelegatorのインスタンスオブジェクトを生成し、初期化する。" ^  deletage: aDelegateObject; filter: aFilterObject. !!<WtXmlEndTag />

委譲用クラスの使用例: | delegator |

delegator := MouseDelegator withDelegate:  withFilter: . delegator click. "Exampleのインスタンスオブジェクトにclickメッセージが送られる。" delegator doubleClick. "OtherExampleのインスタンスオブジェクトにdoubleClickメッセージが送られる。"<WtXmlEndTag />

2.5.1. 参考
---------

[1]

2.6. Go
-------

Go言語においては、他の言語と異なり始めから委譲を想定した委譲専用構文を備えている。 Go言語では、表向き継承機能を持っていないが、この委譲構文によって継承に必要とされる殆どの機能を実現できる。例えば多重継承も可能であるが、基底型から派生型のメンバーを呼び出すようなことは出来ない。

下記にGoの委譲機能を使って、線分の頂点座標しか表示できない型に対して、Bezier曲線の頂点座標表示能力を付与する例を示す。 type Point2D struct { X, Y float64 }

func AxisX float64 { return point.X }

func AxisY float64 { return point.Y }

/* 線分描画機能を定義した型 */ type SimpleContext interface { Location Point2D MoveTo LineTo }

/* 線分描画及びBezier曲線描画機能を定義した型 */ type BezierContext interface { Location Point2D MoveTo LineTo CubicBezierTo }

/* SimpleContext型にBezier曲線描画機能を付与する型 */ type BezierAdapterContext struct { /* 委譲先の型名を記述する事でBezierAdapterContextに対し呼び出された Location、MoveTo、LineToは、記述した型に自動で委譲される。

/SimpleContext }

func  CubicBezierTo { start := this.Location

cubicBezier := func float64, t float64 ) float64 { dis_t := 1.0 - t

return axis * math.Pow + axis * math.Pow + 3 * t * dis_t *  * dis_t * + axis * t ) }

split := 10 for i := 0; i < split; i++ { t := float64 / float64

this.LineTo, Y:cubicBezier, } ) } }

/* 線分の現在地管理し、入力された線分座標を表示する型 */ type BasicContext struct { current Point2D }

func  Location Point2D { return this.current }

func  MoveTo { this.current = point }

func  LineTo { this.MoveTo fmt.Printf }

func main { /* BezierAdapterContext型からBasicContext型への委譲関係構築。 BasicContextに対し、Bezier曲線の座標を表示する能力が付与される。

/base_context := BasicContext{} var context BezierContext = &BezierAdapterContext{ SimpleContext: &base_context }

/* BasicContext型に委譲される */ context.MoveTo context.LineTo

/* BezierAdapterContext型が直接処理する */ context.CubicBezierTo }<WtXmlEndTag />

2.7. Ruby
---------

crubyでは標準添付のdelegateライブラリを利用して委譲が使える。

3. 関連項目
-------

デザインパターン 
デリゲート 
トレイト