委譲
委譲 (delegation) とはオブジェクト指向プログラミングにおいて、あるオブジェクトの操作を一部他のオブジェクトに代替させる手法のこと。
目次 |
概要 [編集]
委譲を行うオブジェクトは委譲先オブジェクトへの参照を持ち、必要に応じてその参照を切り替える事で動作にバリエーションを持たせる事ができる。一種の実装遅延、プラグイン機構である。一例としては、オブジェクトの編集を行う時、編集の前処理、後処理を本処理と独立させ委譲先に任せる事で、オブジェクト本体の変更を最小限にとどめ局所性を向上させる、などがある。
操作の代替という観点では他に代理 (Proxy) と呼ばれる手法があるが、この場合は代理側のオブジェクトが実体への参照を保持する事で操作のフィルタを行う概念であり、実装の分離を目的とする委譲とは異なる。
委譲を引き受けるオブジェクトはどのような操作を実装しなければならないか知っている必要があるため、インタフェースと併用される場合が多い。
いくつかの言語ではデザインパターンにおける手法であり、いくつかの言語ではコードで実装されライブラリとして提供されている。
例 [編集]
C++ [編集]
#include <iostream> struct ExampleInterface { virtual void Function() = 0; }; class Something { public: void Print() { std::cout << "Something::Print() is called." << std::endl; } }; class SomethingDelegator:public ExampleInterface { Something *something; public: SomethingDelegator( Something *something ):something( something ) { } void Function() { // SomethingDelegatorに対するFunction関数の呼び出しをSomethingのPrint関数に委譲している something->Print(); } }; int main() { Something source; SomethingDelegator delegate( &source ); // SomethingDelegatorがSomethingに委譲した事によりSomethingのPrint関数が呼ばれる ExampleInterface &example = delegate; example.Function(); return 0; }
Java [編集]
class A { public void foo() { System.out.println("A.foo() is called."); } } class B { private A a = new A(); public void bar() { a.foo(); } }
Objective-C [編集]
Objective-Cの例を説明する。Objective-Cではデリゲート先のオブジェクトはどんなオブジェクトでも構わない。デリゲートしないときはnilを指定してもよい。Objective-CはC++とは異なり実行時解決を採用しているので、デリゲート先に指定したオブジェクトが必要なメソッドを持っていなくてもコンパイルエラーにはならず、単に何も実行されないだけである。文法上で特殊な扱いはされておらず、平時の記述の枠内で処理される典型的な実装パターンとして用いられる。
あるオブジェクトAがメインウィンドウを持っていて、そのAにメインウィンドウの処理を委譲する場合
[mainWindow setDelegate: self];
selfは自分自身を指すポインタである。[ ]内はC言語に対してObjective-Cで拡張された文法で、インスタンス変数mainWindowに対してsetDelegateメッセージを送っている。
このdelegateを設定することで、Aがメインウィンドウの処理、たとえばウィンドウを閉じる前に何かしたい場合に用いるメソッドwindowWillCloseをかわりに実行するようになる。Aに
-(void)windowWillClose:(NSNotification*)notification { // 処理 }
のようなメソッドを用意しておけば、ウィンドウのクラス(NSWindow)に修正を加えたり、NSWindowを継承したクラスを作らなくても振る舞いをカスタマイズすることができる。
Smalltalk [編集]
Smalltalkにおける委譲も基本的には他の言語と同じである。 ただし、メッセージ機構を利用することでSmalltalk独特の委譲を行うことが出来る。 Smalltalkは、オブジェクトに対しメッセージが送られた際、メッセージ内のセレクターに該当するメソッドがオブジェクト内に無ければ、そのメッセージはdoesNotUnderstand:メソッドに送られる。この時doesNotUnderstand:メソッドで受け取ったメッセージは自由に他のオブジェクトに送りつけることが出来る。
なお、Objective-CにもdoesNotUnderstand:と同等の仕組みが存在し、同様の処理を記述できる。上記のObjective-Cの委譲もこのメッセージ処理の仕組みを利用している。
委譲用クラスの定義例:
"Smalltalk環境にDelegatorクラスを登録( 定義 )する。" Object subclass: #MouseDelegator "ObjectクラスからDelegatorクラスを派生させる。" instanceVariableNames: 'aTarget anOtherTarget' "委譲先のフィールド( メンバー変数 )を定義" classVariableNames: '' poolDictionaries: '' category: 'ExampleDelegate'.
"MouseDelegatorに登録するaccessorの定義( aTargetフィールド用 )" target ^aTarget target: anObject aTarget := anObject.
"MouseDelegatorに登録するaccessorの定義( anOtherTargetフィールド用 )" otherTarget ^anOtherTarget. otherTarget: anObject anOtherTarget := anObject.
"MouseDelegatorに登録するインスタンスメソッドclickの定義" click "clickメッセージをaTargetに委譲する" self target click.
"MouseDelegatorに登録するメソッドdoesNotUnderstand:の定義" doesNotUnderstand: aMessage "click以外のメッセージは全てaOtherTargetに委譲する。" aMessage sendTo: self otherTarget.
"MouseDelegatorに登録するクラスメソッドwithTarget: withOtherTarget:の定義" withTarget: aTarget withOtherTarget: anOtherTarget "MouseDelegatorのインスタンスオブジェクトを生成し、初期化する。" ^ ( self new ) target: aTarget; otherTarget: anOtherTarget.
委譲用クラスの使用例:
| delegator | delegator := MouseDelegator withTarget: ( Example new ) withOtherTarget: ( OtherExample new ). delegator click. "Exampleのインスタンスにclickメッセージが送られる。" delegator doubleClick. "OtherExampleのインスタンスにdoubleClickメッセージが送られる。"
参考 [編集]
Go [編集]
Go言語においては、他の言語と異なり始めから委譲を想定した委譲専用構文を備えている。 Go言語では、表向き継承機能を持っていないが、この委譲構文によって継承に必要とされる殆どの機能を実現できる。例えば多重継承も可能であるが、基底型から派生型のメンバーを呼び出すようなことは出来ない。
下記にGoの委譲機能を使って線分の頂点座標しか表示できない型に対して、Bezier曲線の頂点座標表示能力を付与する例を示す。:
type Point2D struct { X, Y float64 } func AxisX( point Point2D ) float64 { return point.X } func AxisY( point Point2D ) float64 { return point.Y } /* 線分描画機能を定義した型 */ type SimpleContext interface { Location() Point2D MoveTo( point Point2D ) LineTo( point Point2D ) } /* 線分描画及びBezier曲線描画機能を定義した型 */ type BezierContext interface { Location() Point2D MoveTo( point Point2D ) LineTo( point Point2D ) CubicBezierTo( control1, control2, point Point2D ) } /* SimpleContext型にBezier曲線描画機能を付与する型 */ type BezierAdapterContext struct { /* 委譲先の型名を記述する事でBezierAdapterContextに対し呼び出された Location、MoveTo、LineToは、記述した型に自動で委譲される。 */ SimpleContext } func ( this *BezierAdapterContext ) CubicBezierTo( control1, control2, end Point2D ) { start := this.Location() cubicBezier := func( axis func( Point2D ) float64, t float64 ) float64 { dis_t := 1.0 - t return axis( start ) * math.Pow( dis_t, 3 ) + axis( end ) * math.Pow( t, 3 ) + 3 * t * dis_t * ( axis( control1 ) * dis_t * + axis( control2 ) * t ) } split := 10 for i := 0; i < split; i++ { t := float64( i ) / float64( split ) this.LineTo( Point2D { X:cubicBezier( AxisX, t ), Y:cubicBezier( AxisY, t ), } ) } } /* 線分の現在地管理し、入力された線分座標を表示する型 */ type BasicContext struct { current Point2D } func ( this *BasicContext ) Location() Point2D { return this.current } func ( this *BasicContext ) MoveTo( point Point2D ) { this.current = point } func ( this *BasicContext ) LineTo( point Point2D ) { this.MoveTo( point ) fmt.Printf( "%f @ %f\n", point.X, point.Y ) } func main() { /* BezierAdapterContext型からBasicContext型への委譲関係構築。 BasicContextに対し、Bezier曲線の座標を表示する能力が付与される。 */ base_context := BasicContext{} var context BezierContext = &BezierAdapterContext{ SimpleContext: &base_context } /* BasicContext型に委譲される */ context.MoveTo( Point2D{ X:0, Y:0 } ) context.LineTo( Point2D{ X:20, Y:20 } ) /* BezierAdapterContext型が直接処理する */ context.CubicBezierTo( Point2D{ X:50, Y:-100 }, Point2D{ X:100, Y:100 }, Point2D{ X:200, Y:0 } ) }
Ruby [編集]
crubyでは標準添付のdelegateライブラリを利用して委譲が使える。