SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

japan.internet.com翻訳記事

階層化アーキテクチャと依存性注入・依存性逆転

アプリケーションを階層化して構築する考え方の紹介

依存性逆転の原則

 前にも述べたとおり、今回の例ではPassive Viewパターン(具体的にはビューのインターフェイス(抽象))を導入することで、依存性逆転の原則を密かに取り入れています。ビューインターフェイスのコードは以下のようになります。

public interface IEmployeeView
{
   event EventHandler Load;
   bool IsPostBack { get; }
   IList<IEmployee> Employees { set; }
}

 とても単純ですね。なお、このビューインターフェイスにはLoadイベントが定義されていますが、Webページのコードビハインドには明示的な実装が見当たらなかったはずです。なぜなら、すべてのWebページで既に(同じデリゲートシグネチャの)Loadイベントを定義しており、これでこのインターフェイスの要件が満たされるからです。IsPostbackプロパティについても同じことが当てはまります(こちらはPageクラスで満たされます)。それなら、なぜインターフェイスを導入するのでしょうか。その理由は、インターフェイス(または抽象)にコーディングしておけば、そのインターフェイスを実装している任意のオブジェクトにプレゼンターがアクセスできるようになるからです。WindowsフォームでもモバイルWebコントロールでもWebページでも、それがビューインターフェイスを実装してさえいればアクセス可能です。プレゼンターは常に抽象(インターフェイス)を通じてその実装にアクセスするので、実装の詳細を気にかけません。これは依存性逆転の原則の重要性をよく示しており、次のように言い表すことができます。

「高レベルのコンポーネントは低レベルのコンポーネントに依存してはならない。両方ともインターフェイスに依存すべきである」

 今度は、この原則に従ってプレゼンターのコードをさらに整理することにします。

 以降、Webページとそれに対応するコードビハインドに手を加えることはないので、これらは頭の隅に追いやってかまいません。これは階層化アーキテクチャのメリットであり、周囲に余計な影響を与えずにコードの大幅改良ができるという特徴をよく示しています。次はプレゼンターに目を転じ、これを「高レベル」コンポーネントとして扱うことにします。プレゼンターは現在どんな高レベルコンポーネントに依存しているのでしょうか。以下のコードから2つのヒントが得られるはずです。

using (connection = new 
   SqlConnection(ConnectionString))
{
   SqlCommand command = connection.CreateCommand();
   command.CommandText = "SELECT * FROM Employees";
   command.CommandType = CommandType.Text;
   connection.Open();
   using (SqlDataReader 
      reader = command.ExecuteReader(
         CommandBehavior.CloseConnection))
   {
      DataTable results = new DataTable();
      results.Load(reader);
      return results;
   }
}

 このコードでは、プレゼンターをSqlConnectionSqlCommandSqlDataReaderというオブジェクトに結合しています。ここで一番問題なのは、この機能はプロジェクトのプレゼンテーション層に属していないという点です。表示だけを担当すべきプレゼンテーション層を、アプリケーションへのデータ提供を行う物理データベースに結合していますが、これはやはり不必要な結合です。

 どうすればこの問題に対処できるでしょうか。コンピュータサイエンスの古い格言に、「どんな問題でも間接的な層をもう1つ追加すれば解決できる」というものがあります。今回は、プロジェクトにサービス層を追加することでプレゼンターのコードを整理することにします。サービス層がどのようなものかは、この記事に詳しく書かれています。本稿のシナリオで注目していただきたいのは、サービス層をファサードとして使用することにより、プレゼンテーション層をアプリケーション機能(本稿の例では一連の従業員データを取得すること)の実行の詳細から隔離しているという点だけです。今回の例では、依存性逆転を利用して、「すべての従業員を取得する」という機能を備えたサービス層コンポーネントに関するコントラクト(抽象)を導入します。

 プレゼンターの主要な役割は、従業員のリストをビューに提供することです。プレゼンターは、このリストがどのように取得されるかは関知しません。それはサービス層の領分です(最終的にはマッパー層ですが、これについては本稿では触れません)。プレゼンターの主要な役割を確認したところで、このプレゼンターからアクセスされるサービス層コンポーネントのインターフェイスを導入します。

public interface IEmployeeTask
{
   IList<IEmployee> 
   GetAllEmployees();
}

 ご覧のように、これも単純なインターフェイスです。依存性逆転の原則に従うことで、プレゼンターはビューとサービス層に依存するようになりました。サービス層を導入する前のプレゼンターは本来の責任以上のものを抱えていましたが、この部分は最終的にサービス層へと分離されます。コントラクト(抽象)へのコーディングの利点は、プレゼンターがコントラクトにのみアクセスすればよいということです。コンパイル時にプレゼンターは、そのコントラクトが実装されているかどうかを知りません(あるいは気にかけません)。そのため、サービス層コンポーネントの実際の具象実装が存在しない段階でも、プレゼンターのコードを完成させることができます。リスト3に、下層コンポーネントへの依存を導入したリファクタリング後のプレゼンターを示します。

リスト3 サービス層インターフェイス導入後のプレゼンター
public class ViewEmployeesPresenter
{
   private IEmployeeView view;
   private IEmployeeTask task;

   public ViewEmployeesPresenter(IEmployeeView
      view, IEmployeeTask task)
   {
      this.view = view;
      this.task = task;
      HookupEventHandlersTo(view);
   }

   private void HookupEventHandlersTo(IEmployeeView view)
   {
      view.Load += delegate { LoadEmployees(); };
   }

   private void LoadEmployees()
   {
      if (view.IsPostBack) return;
      view.Employees = task.GetAllEmployees();
   }
}

 これで見違えるほどすっきりしました。依存性逆転の原則を利用してプレゼンターをリファクタリングすることで、はるかに凝集度の高いユニットになりました。プレゼンターは、(インターフェイスを通じて)ビューにデータを提供する責任を負い、さらにコントラクト(インターフェイス)を通じてサービス層コンポーネントからデータを取得します。実は、このプレゼンターのリファクタリングにあたり、依存性逆転をさらに有効に活用するためのテクニックも密かに取り入れていました。それは依存性注入です。

依存性注入

 リスト3をよく見ると、プレゼンターのコンストラクタで非常に重要なことが行われているのに気づくでしょう。

public 
  ViewEmployeesPresenter(IEmployeeView
  view, IEmployeeTask task)

 たとえプレゼンターが自身の果たすべき凝集的な責任をいくつか持っているとしても、その依存性を把握していなければ、責任を果たすことはできません。そうした依存性は作成時にプレゼンターに与えられます。これはコンストラクタベースの依存性注入と呼ばれるタイプの依存性注入です。

 依存性注入の背後にある主要な考え方は、「オブジェクトが自らの仕事を遂行するために他のコンポーネントに頼る場合には、そのオブジェクト自身がコンポーネントの作成責任を負うのではなく、コンポーネントを抽象という形でそのオブジェクトに注入すべきだ」というものです。

 最もよく知られたタイプの依存性注入は、コンストラクタ注入セッター注入です。

 コンストラクタ注入では、依存性を持つオブジェクトのインスタンスが作成されるときに、必要な依存性がすべて与えられます。そのオブジェクトインスタンスが何によって作成されるかは関係ありません。そのオブジェクトが自らの仕事を遂行するために必要なものが、インスタンス作成時にすべて与えられなければなりません。必要なものがすべて与えられなかった場合(依存性としてnullが渡された場合など)、そのオブジェクトは仕事を遂行できません。コンストラクタベースの注入の1つの短所は、オブジェクトの依存性が多いと、コンストラクタで多くの依存性を指定しなければならないので、やや扱いにくくなるかもしれないという点です(もちろん、ファクトリを使って、これをプログラマから見えなくすることも可能です)。

 セッター注入では、少し違ったテクニックを使用し、ほとんど常にファクトリに頼って、オブジェクトを作成し、そこに依存性を結び付けます。セッター注入では、依存性を持つオブジェクトに特別なコンストラクタはありません。その代わり、オブジェクトのすべての依存性は、そのオブジェクトが依存性のために公開しているセッタープロパティを通じて、ファクトリによって注入されます。セッターベースの注入の短所は、ファクトリを省略した場合に、そのオブジェクトを使用するコンポーネントを、そのオブジェクトが必要とする依存性の具象実装に結合しなければならないという点です。

 依存性注入の概念全体は、抽象へのプログラミングという概念にかかっています。本稿のプレゼンターは、以下のものへの依存を持っています。

  • 提供された情報を表示できる「ビュー」
  • プレゼンターに代わってアプリケーション機能を実行できる「サービス」

 テスト容易性の観点から見ると、依存性逆転と依存性注入の両方を利用すれば、テスト容易性を高めることができ、またアプリケーションのコンポーネントどうしの結合度を下げることができます。以下のコードは、単体テストとモックオブジェクトを組み合わせて、プレゼンターが構築時にビューインターフェイスのLoadイベントをサブクスライブすることを検証する方法を示しています。

[Test]
public void ShouldSubscribeToViewEventsOnConstruction()
{
    MockRepository mockery = new MockRepository();
    IEmployeeView mockView = mockery.CreateMock<IEmployeeView>();
    IEmployeeTask mockTask = mockery.CreateMock<IEmployeeTask>();
    mockView.Load += delegate { };
    LastCall.IgnoreArguments();
    mockery.ReplayAll();
    ViewEmployeesPresenter presenter = new 
      ViewEmployeesPresenter(mockView, mockTask);
    mockery.VerifyAll();
}

 このコードでは、Rhino MocksというモックオブジェクトフレームワークとNUnit単体テストフレームワークを使用しています(私はテスト駆動開発の実践者なので、プレゼンターの機能を試すために通常ならこのテストを先に書きます。しかし、本稿では話の焦点をはっきりさせるためにテストを後回しにしました)。このテストをパスするために必要なコードは、プレゼンタークラス自体の中にあります。

public ViewEmployeesPresenter(IEmployeeView view, 
   IEmployeeTask task)
{
   this.view = view;
   this.task = task;
   HookupEventHandlersTo(view);
}
   
private void HookupEventHandlersTo(IEmployeeView view)
{
    view.Load += delegate {
       LoadEmployees(); };
}

 コンストラクタがHookupEventHandlersToメソッドを呼び出すことに注意してください。このメソッドはビューインターフェイスで定義されたLoadイベントをサブスクライブします。

 メンテナンス性の観点から言えば、これでプレゼンターが構築時に必ずビューのLoadイベントをサブスクライブすることを確認できるようになりました。もちろん、実際の機能が実行されるのはビューのLoadイベントがトリガされたときです。これでプレゼンターがサービス層と通信して、全従業員のリストを要求することができます。プレゼンターは得られたものをビューに返します。依存性注入を利用することによって、このシナリオのテストを書くのが楽になりました。「実際の」オブジェクトの代わりにモックオブジェクトを使用しても、プレゼンターはやはり気がつきませんでした。というのも、プレゼンターは実装ではなく抽象に依存しているからです。

 これで、プレゼンターは単に自身の依存物に基づき、依存物に対して反応を返すコンポーネントに格下げされました。つまり、プレゼンターがコンシュームするコンポーネントが、すべてのアクションが行われるべき場所になります。この後はサービス層に目を転じ、依存性逆転と依存性注入のテクニックを利用してIEmployeeTaskインターフェイスのクリーンな実装を作成する方法を示すことにします。

次のページ
サービス層における依存性注入と依存性逆転

この記事は参考になりましたか?

japan.internet.com翻訳記事連載記事一覧

もっと読む

この記事の著者

japan.internet.com(ジャパンインターネットコム)

japan.internet.com は、1999年9月にオープンした、日本初のネットビジネス専門ニュースサイト。月間2億以上のページビューを誇る米国 Jupitermedia Corporation (Nasdaq: JUPM) のニュースサイト internet.comEarthWeb.com からの最新記事を日本語に翻訳して掲載するとともに、日本独自のネットビジネス関連記事やレポートを配信。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

Jean-Paul S. Boodhoo(Jean-Paul S. Boodhoo)

.NET 1.0のベータ1から.NET Frameworkに従事してきた.NET開発のエキスパートで、アプリケーションのアーキテクチャ作成と設計と開発で7年以上の経験がある。アジャイルプラクティスと実際的なビヘイビア駆動開発(BDD)テクニックを通じてチームの成功を支援する独立コンサルタントとして活躍している。BDDを.NETに応用する記事をVisual Studio Magazine、DevX、MSDNに寄稿。ポッドキャス...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1622 2007/09/04 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー