階層化アーキテクチャによる責任の分割
リスト1の主な問題点の1つは、単一責任の原則を完全に放棄していることです。単一責任の原則とは、簡単に言えば「あらゆるオブジェクトは責任を1つしか持ってはならず、したがって変更する理由は1つでなければならない」というものです。この原則に従っているコンポーネントは一般に、「凝集度の高い」コンポーネントと表現されます。
もしも質問する機会があったなら、私は「この従業員表示Webページの主な責任は何ですか」と尋ねたことでしょう。この質問に対する答えはおそらく、「必要なのは従業員データをユーザーに表示することだけだ」となります。ところが、コードビハインド(code behind:分離コード)の内容を見ると、実態はまったく違っています。現在、このコンポーネントは従業員データをユーザーに表示するという1つの責任を負う代わりに、以下のすべての責任を負っています。
- データベースへの接続を作成する
- データベースから適切な情報を引き出すためのSQLステートメントを作成する
- 高コストのリソース(接続やリーダーなど)を処理する
- データベースの情報をデータのドメイン表現にマップする
- ユーザーに情報を表示する
この短いリストを見てもわかるように、このコンポーネントが負っている責任は多すぎます。さらに悪いことに、これらの責任のうち、従業員のリストを「表示する」ことに関係しているのは1つだけです。これでは凝集度が低いと思わざるを得ません。
大多数の開発者は、アプリケーションにn層アーキテクチャという概念を導入することが、この問題の解決に役立つということにかなり前から気づいていました。要するに、階層化アーキテクチャを導入すれば、各層が(できるだけ実際的に)単一責任の原則をまっとうできるようになります。図2は、アプリケーションを構成する分離された層の概念図です。
以下では、このアプリケーションの全面的なリファクタリングを試みることにします。Webページのコードビハインドにおける単一責任を実現するために、まずModel View Presenterデザインパターンの変形であるPassive Viewパターンを利用します。なお、Model View Presenterパターンの詳細については本稿では説明しませんので、私が昨年書いた記事を参照してください。
最初に行うリファクタリングは、「従業員を表示する」という責任に直接関係しないコードを取り除くことです。実は、これによってコードビハインドからほぼすべてのコードを取り除くことになります。Webページの新たなコードビハインドは以下のようになります。
public partial class ViewEmployeesWithAdditionOfPresenter : Page, IEmployeeView { private ViewEmployeesPresenterLowCohesion presenter; protected override void OnInit(EventArgs e) { base.OnInit(e); presenter = new ViewEmployeesPresenterLowCohesion(this); } public IList Employees { set { this.employeesRepeater.DataSource = value; this.employeesRepeater.DataBind(); } } }
リスト1と比べると、雲泥の差があるでしょう。Passive Viewパターンに不案内な方のために、ここで簡単に説明しておきます。
- ビュー実装(Webページ)は、プレゼンターによってコンシュームされるインターフェイスを実装する。
- ビューインターフェイスは、ビュー(Webページ)によって実装され、プレゼンターによってサブスクライブされるイベントを公開する。
- ビューは自分自身について発生する事象(たとえば、「ボタンがチェックされた」とか「ロードが行われている」など)への応答としてイベントを発生させる。これらのイベントはプレゼンターによって処理される。
- プレゼンターはイベントを処理する。また、ビューインターフェイスを使ってビューに情報を返すことがある。
- ビューインターフェイスを使用すると、プレゼンターは任意のUIテクノロジ(たとえば、ASP.NET)との緩やかな結合を維持できる。
リスト2に、この段階でのプレゼンタークラスを示します。
public class ViewEmployeesPresenterLowCohesion { private IEmployeeView view; private const string ConnectionString = "data source=(local);Integrated Security=SSPI;" + "Initital Catalog=NorthWind"; private SqlConnection connection; public ViewEmployeesPresenterLowCohesion( IEmployeeView view) { this.view = view; HookupEventHandlersTo(view); } private void HookupEventHandlersTo( IEmployeeView view) { view.Load += delegate { LoadEmployees(); }; } private void LoadEmployees() { if (view.IsPostBack) return; view.Employees = 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"])); } }
リスト2からわかるように、ここで行ったのは、もともとWebページのコードビハインドにあった雑多なものを別のクラスに移しただけのことです。ただ1つ注目してほしいのは、ここでビューのインターフェイスを導入することで、柔軟な階層化アーキテクチャを作成する際に重要となる「依存性逆転の原則」という概念を取り入れていることです。

