制御の反転

2005年6月26日

制御の反転は、フレームワークを拡張する際に遭遇する一般的な現象です。実際、フレームワークの決定的な特性と見なされることがよくあります。

簡単な例を考えてみましょう。ユーザーからいくつかの情報を取得するプログラムを作成していて、コマンドラインの問い合わせを使用しているとします。次のようなことをするかもしれません。

  #ruby
  puts 'What is your name?'
  name = gets
  process_name(name)
  puts 'What is your quest?'
  quest = gets
  process_quest(quest)

このやり取りでは、私のコードが制御しています。いつ質問するか、いつ応答を読むか、いつそれらの結果を処理するかを決定します。

ただし、ウィンドウシステムを使用してこのようなことをする場合、ウィンドウを構成することで行います。

  require 'tk'
  root = TkRoot.new()
  name_label = TkLabel.new() {text "What is Your Name?"}
  name_label.pack
  name = TkEntry.new(root).pack
  name.bind("FocusOut") {process_name(name)}
  quest_label = TkLabel.new() {text "What is Your Quest?"}
  quest_label.pack
  quest = TkEntry.new(root).pack
  quest.bind("FocusOut") {process_quest(quest)}
  Tk.mainloop()

これらのプログラムの制御フローには大きな違いがあります。特に、`process_name`メソッドと`process_quest`メソッドがいつ呼び出されるかの制御です。コマンドライン形式では、これらのメソッドがいつ呼び出されるかを制御しますが、ウィンドウの例では制御しません。代わりに、ウィンドウシステムに制御を渡します(`Tk.mainloop`コマンドを使用)。その後、フォームの作成時に作成したバインディングに基づいて、いつメソッドを呼び出すかを決定します。制御は反転されます。つまり、私がフレームワークを呼び出すのではなく、フレームワークが私を呼び出します。この現象は制御の反転(ハリウッドの原則 - 「私たちに電話しないで、私たちから電話します」とも呼ばれます)です。

フレームワークの重要な特性の1つは、フレームワークを調整するためにユーザーが定義したメソッドが、ユーザーのアプリケーションコードからではなく、フレームワーク自体の中から呼び出されることが多いことです。フレームワークは、多くの場合、アプリケーションアクティビティの調整と順序付けにおいてメインプログラムの役割を果たします。この制御の反転により、フレームワークは拡張可能なスケルトンとして機能することができます。ユーザーが提供するメソッドは、特定のアプリケーションに対してフレームワークで定義された汎用アルゴリズムを調整します。

-- ラルフ・ジョンソンとブライアン・フット

制御の反転は、フレームワークをライブラリと区別する重要な要素です。ライブラリは、基本的に呼び出すことができる関数のセットであり、最近は通常クラスに編成されています。各呼び出しはいくつかの作業を行い、制御をクライアントに返します。

フレームワークは、より多くの動作が組み込まれた抽象的な設計を体現しています。それを使用するには、サブクラス化または独自のクラスをプラグインすることにより、動作をフレームワークのさまざまな場所に挿入する必要があります。その後、フレームワークのコードはこれらのポイントでコードを呼び出します。

呼び出されるようにコードをプラグインするには、さまざまな方法があります。上記のRubyの例では、テキスト入力フィールドでbindメソッドを呼び出し、イベント名とラムダを引数として渡します。テキスト入力ボックスがイベントを検出するたびに、クロージャ内のコードが呼び出されます。このようにクロージャを使用するのは非常に便利ですが、多くの言語はクロージャをサポートしていません。

これを行う別の方法は、フレームワークにイベントを定義させ、クライアントコードにこれらのイベントをサブスクライブさせることです。.NETは、人々がウィジェットでイベントを宣言できるようにする言語機能を備えたプラットフォームの良い例です。デリゲートを使用することで、メソッドをイベントにバインドできます。

上記のアプローチ(実際には同じです)は単一の場合にはうまく機能しますが、複数の必要なメソッド呼び出しを単一の拡張ユニットに結合したい場合があります。この場合、フレームワークは、クライアントコードが関連する呼び出しに対して実装する必要があるインターフェースを定義できます。

EJBはこのスタイルの制御の反転の良い例です。セッションBeanを開発する場合、EJBコンテナによってさまざまなライフサイクルポイントで呼び出されるさまざまなメソッドを実装できます。たとえば、Session Beanインターフェースは、`ejbRemove`、`ejbPassivate`(セカンダリストレージに保存)、および`ejbActivate`(パッシブ状態から復元)を定義します。これらのメソッドがいつ呼び出されるかを制御することはできません。コンテナが私たちを呼び出し、私たちはそれを呼び出しません。

これらは制御の反転の複雑なケースですが、はるかに単純な状況でこの効果に遭遇します。テンプレートメソッドが良い例です。スーパークラスは制御フローを定義し、サブクラスはメソッドをオーバーライドするか、抽象メソッドを実装して拡張します。そのため、JUnitでは、フレームワークコードが`setUp`メソッドと`tearDown`メソッドを呼び出して、テストフィクスチャを作成およびクリーンアップします。呼び出しを行うのはフレームワークコードであり、あなたのコードは反応します。そのため、ここでも制御は反転されます。

最近では、IoCコンテナの台頭により、制御の反転の意味について混乱が生じています。一部の人々は、ここでの一般的な原則と、これらのコンテナが使用する制御の反転の特定のスタイル(依存性注入など)を混同しています。IoCコンテナは一般にEJBの競合相手と見なされていますが、EJBは制御の反転を同じくらい(それ以上ではないにしても)使用しているため、この名前はやや混乱を招きます(そして皮肉です)。

語源:私が知る限り、制御の反転という用語は、1988年にオブジェクト指向プログラミングジャーナルによって発行されたジョンソンとフットの論文再利用可能なクラスの設計で最初に明らかになりました。この論文は、よく熟成されたものの1つです。15年以上経った今でも読む価値があります。彼らはどこかからこの用語を得たと思っていますが、どこからかは覚えていません。その後、この用語はオブジェクト指向コミュニティに浸透し、GoF(Gang of Four)ブックに登場します。よりカラフルな同義語「ハリウッドの原則」は、1983年のMesaに関するリチャード・スウィートの論文に由来しているようです。設計目標のリストで、彼は次のように書いています。「私たちに電話しないで、私たちから電話します(ハリウッドの法則):ツールは、「ユーザーにコマンドを要求して実行する」モデルを採用するのではなく、ユーザーがツールに何らかのイベントを伝えたいときに、Tajoがツールに通知するように調整する必要があります。」ジョン・ヴリスィデスは、C ++レポートのコラムを執筆し、「ハリウッドの原則」というモニカの下でこの概念についての良い説明を提供しています。(語源について私を助けてくれたブライアン・フットとラルフ・ジョンソンに感謝します。)