サービス層における依存性注入と依存性逆転
リスト4は、プレゼンターから取り除いた部分のコードです。さて、このコードはドードー鳥のように消えていく運命なのでしょうか?
public DataTable GetEmployees() { 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; } } } private IList<IEmployee> MapFrom(DataTable employeeData) { List<IEmployee> employees = new List<IEmployee>(); foreach (DataRow employeeRow in employeeData.Rows) { employees.Add(MapFrom(employeeRow)); } return employees; } private Employee MapFrom(DataRow row) { return new Employee(Convert.ToInt32( row["EmployeeId"]), row["LastName"].ToString(), row["FirstName"].ToString(), row["Title"].ToString(), Convert.ToDateTime(row["BirthDate"]), Convert.ToDateTime(row["HireDate"])); }
この部分を処理するために、サービス層にIEmployeeTaskインターフェイスの実装を作成します。リスト5は私が最初に作成したサービス実装ですが、これはあまり良い方法ではありません。
public class EmployeeTaskLowCohesionNoDependencyInversion : IEmployeeTask { public const string ConnectionString = "data source=(local);Integrated Security=SSPI;" + "Initial Catalog=Northwind"; private SqlConnection connection; public IList<IEmployee> GetAllEmployees() { return MapFrom(GetEmployees()); } public DataTable GetEmployees() { 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; } } } private IList<IEmployee> MapFrom(DataTable employeeData) { List<IEmployee> employees = new List<IEmployee>(); foreach (DataRow employeeRow in employeeData.Rows) { employees.Add(MapFrom(employeeRow)); } return employees; } private Employee MapFrom(DataRow row) { return new Employee(Convert.ToInt32( row["EmployeeId"]), row["LastName"].ToString(), row["FirstName"].ToString(),row["Title"].ToString(), Convert.ToDateTime(row["BirthDate"]), Convert.ToDateTime(row["HireDate"])); } }
ユーザーインターフェイスとプレゼンターがすっきりしたのはいいのですが、問題をサービス層にシフトさせただけで、同じ過ちを繰り返したのでは、そもそも今回のプロジェクト改善に取り組み始めた意味がありません。依存性逆転と依存性注入という新しい知識を踏まえて、今度は抽象ではなく実装への依存を整理することにしましょう。まずは新しいインターフェイスを導入します。
public interface IConnectionFactory { IDbConnection Create(); }
IConnectionFactoryインターフェイスを作成したので、依存性注入を使用して、サービス層クラスがこのIConnectionFactoryを使って構築されるようにします。
public EmployeeTaskLowCohesion NoDependencyInversion( ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; }
このようなわずかな変更により、GetEmployeesメソッドのコードを以下のように変更することが可能になります。
public DataTable GetEmployees() { using (IDbConnection connection = connectionFactory.Create()) { using (IDbCommand command = connection.CreateCommand()) { command.CommandText = "SELECT * FROM Employees"; command.CommandType = CommandType.Text; connection.Open(); using (IDataReader reader = command.ExecuteReader( CommandBehavior.CloseConnection)) { DataTable results = new DataTable(); results.Load(reader); return results; } } } }
もはやGetEmployeesメソッドが明示的なSqlClient実装にコーディングされていないことに注意してください。現在のGetEmployeesメソッドは、System.Data名前空間で定義されている抽象を使用しており、ほんの少し手間をかけるだけで、このコンポーネントで任意のデータベースをシームレスに扱うことが可能になっています。違いがあるとすれば、特定のデータベース(Oracle、SQL、MySQLなど)への接続を作成するためにプラグインしなければならないIConnectionFactoryの実装だけです。このコーディングスタイルのもう1つの副産物は、実際のデータベースを指定しなくても、このクラスの動作を簡単にテストできることです。
では、IEmployeeTaskの現在の実装はもう不要になったのでしょうか? 決してそんなことはありません。私の考えでは、現在の実装には他のオブジェクト(あるいはまったく異なる層)に移すべき責任がまだ数多く残っています。本稿の残りの部分では、抽象をこれ以上取り上げるのはやめて、これらの依存物を互いに結び付けることについて論じることにします。私が実際にサービス層クラスをどのように整理したかと、さまざまな責任をどのような層に分割したかについては、本稿のサンプルソースコードを参照してください。
全体の組み立て
これらの抽象と依存があちこちに注入されるわけですが、「実際に依存を注入するのは誰か」と疑問に思う人もいるでしょう。ここでプレゼンターのコードと、対応するWebページをもう一度見てみましょう。プレゼンターのコンストラクタを思い出してください。
public ViewEmployeesPresenter(
IEmployeeView view, IEmployeeTask task)
プレゼンターを構築するには、プレゼンターが情報を返すビューと、プレゼンターが情報を要求するサービス層の両方を与える必要があります。ここで問題が起こります。なぜなら、Webページのコードビハインドで、そのページに関するプレゼンターをインスタンス化する必要があるからです(他のソリューションもありますが、本稿の趣旨から外れるので、そうしたものには触れません)。
プレゼンターがビューへの依存を持っているだけなら次の行で用が足りるのですが、あいにくそうではありません。プレゼンターは作成時にビューとサービス層の両方の依存を持たねばなりません。
presenter = new ViewEmployeesPresenter(this);
この問題を解決する簡単な方法として考えられるのは、Webプロジェクトからサービス層プロジェクトへの参照を追加し、Webページのコードビハインドで次のコードを実行することです。
presenter = new ViewEmployeesPresenter( this, new EmployeeTask());
残念ながら、こうするとビューはプレゼンターを作成する責任を負うだけでなく、プレゼンターに必要な依存物を作成して、そこに結合するという処理も担当しなければなりません。そうなると、そもそもこの道を選んだ意味がなくなりそうです。
この問題を解決するには、非常に単純で結合度の高い方法から、もっと複雑で結合度の低い方法まで、数多くの方法が考えられます。手始めに非常に単純なアプローチを見てみましょう。
まず、プレゼンターを構築するためにWebページで行う処理は次の1行だけにしたいと思います。
presenter = new ViewEmployeesPresenter(this);
これを実現するには、コンストラクタチェーンを利用して、ビューがコンシュームできる単純なコンストラクタを提供します。この「簡易版」コンストラクタは、必要なものを提供してくれる「完全版」コンストラクタ(すべての依存を必要とするコンストラクタ)を呼び出します。
public ViewEmployeesPresenter( IEmployeeView view):this(view, new EmployeeTask()) { } public ViewEmployeesPresenter( IEmployeeView view, IEmployeeTask task)
これでビューは、プレゼンターが必要とする一切の依存(それ自身を除く)について何も知らなくて済みます。これは単純なソリューションであり、プレゼンターと1つの依存物の特定の実装とを結合することになります。しかし、この結合はそれほど恐れるようなものではありません。この結合が発生するのは「簡易版」コンストラクタの中だけだからです。プレゼンター内の他の部分では、常にインターフェイスを通じて自分の依存物とやり取りします。ここで結合が生じるのは、プレゼンターが自らの依存物(IEmployeeTask実装)を作成する責任を負うようになったからです。もちろん、実利的な立場から言えば、もしも読者が依存性注入と依存性逆転という概念を自分自身のアプリケーションに導入しようとするならば、このようにほどほどの結合度を持つやり方から始めるのがよいでしょう。
しかし、依存を持つクラスを依存物の実装にまったく結合しないようにしたい場合は、サービスロケータの機能を利用します。
