(What is)"Interface Partitioning"
実装とインターフェースを分離すること。
(Why)なぜする必要があるのか?
実装に対する変更が、クライアントに影響を及ぼさないようにするため。
各パーツごとに開発できるようになることで、開発効率を上げるため。
Introduction to Interface Partitioning
Interfaceは名刺のようなものです。
コンポーネントが何をするか、何を結果として返すか、処理の実現のために何が必要か(引数など)をクライアントに対して明確に表明する必要がある。
Interfaceの設計に際しては、以下の点に気をつけること。
1. コンポーネントの仕様と責務
- コンポーネントはどんなクライアントからどのように使われるかについて制御できない。そのため適切な使用を可能にし、不適切な使用を防ぐために仕様と責務を明示する必要がある。
2. Interfaceの疎結合と安定性?
- あるクライアントがあるインターフェースを使用している。そのインターフェースには三つ(A, B, C)の機能がある。しかしその機能の内で、クライアントが実際に使用しているのはA, Bだけである。その機能の内、Cに対して変更を加える。クライアントはCを使用していないので、この機能変更がクライアントに影響を与えてはいけない。
この場合、もしかするとインターフェースに複数の機能を詰めすぎているのかもしれない。
(Examples)重要なパターンたち
- Explicit Interface Pattern
- 一番の上位概念
- Extension Interface Pattern
- インターフェースを疎結合にしましょうということ
- Proxy Pattern
- クライアントからコンポーネントに対するアクセスを代理する
- Facade Pattern
- 複数のまとまったコンポーネントに対するエントリーポイントを提供する
- Combined Pattern
- Iterator Pattern
- Enumeration Method Pattern
- Batch Method Pattern
それぞれのパターンについて
1. Explicit Interface Patterns
実装の中身とコンポーネントのインターフェースを分離しましょうという話。
Explicit Interfaceに対してパラメータを渡したり、結果を受け取るには Data Transfer Objectを使用する。具体的なデータ構造に依存しないようにするため。
クライアントがコンポーネントの状態について知りたい場合は Mementoを使用して、カプセル化を維持する。
2. Extension Interface Pattern
"Introduction to Interface Partitioning" > 2で説明した内容に関する話です。
インターフェースに対する機能追加や修正の影響を可能な限りクライアントに波及させないようにしましょうということです。
その変更が、クライアントが未使用であるインターフェースの機能に対する修正だったり、新しい機能の追加だったりする場合には、特に注意を払いたいところです。
3. Proxy Pattern
クライアントが実際のコンポーネントにアクセスする代わりにプロキシ(代理人の意)にアクセスします。
プロキシが、クライアントに変わって実際のコンポーネントにアクセスを行います。
このときに、プロキシがリクエストに必要な前処理、リクエスト自体、リクエスト後に必要な後処理を行います。(なんだかCombined Method Patternに似ている気がします。)
プロキシへとコンポーネントへの呼び出し(インターフェース)を同一にすることで、クライアントはどちらにアクセスしているのかわからなくなります。(疎結合)
プロキシを使用する利点は以下の場合を考えてみると明らかになります。(プロキシではないですが)
4. Facade Pattern
実装の内容をクライアントから隠蔽することをより抽象度の高いレベルで実現しようとしているパターンです。
例えば、ECサイトの例を出してみます。(勝手な空想なので、本当にこうなっているのかはわかりません。言いたいことの大枠をつかんでいただければと思います。)
例えば、ブラウザから検索を行います。
このとき、クライアントであるControllerからリクエストを送り、その結果を表示するまでに以下の処理が走ると思います。
1. 検索文字列に不正な文字列が含まれていないかをチェックする。
2. 検索文字列を元にDBに問い合わせを送る。
3. 検索結果を元に表示内容を整形する。
- 0件なら、「検索結果がありません」と表示する。
- 1件以上なら、それぞれをリストにして表示する。
4. 表示する。
このとき、Controllerが4つのすべてのステップを実行すると検索のプロセスに対してこのコントローラーは密結合になりますね。
例えば、不正な文字列チェックのロジックが変わればControllerも変更を余儀なくされます。
また、3の手順で10件以下の場合に違う表示をしたい(例えば検索条件が近いリストを出す。そのためにリストを取得し直す)となれば、Controllerがそのロジックを実装しなければならないですね。
しかし、これを検索実行インターフェースを作成し、検索のロジックをすべてControllerから隠蔽することで疎結合に変えることができます。これがFacadeのやってることです。
検索という処理に対して、一つのエントリーポイントをクライアントに与えて、処理の詳細はクライアントから隠蔽します。
5. Combined Pattern
ある処理がある組み合わせと順番で呼ばれなければならないものとします。
例えば、以下の処理が順番で呼ばれなければいけないと仮定します。
1. ユーザ認証
2. マスターデータの取得
3. 新着履歴の取得
この処理を呼び出すのをクライアントに任せて置くのは現実的ではありません。
なぜなら、これがアプリケーションのいたるところに書かれているとしたらコードの重複になってしまいます。
また同じ呼び出しを何度も書くのは開発者にとってめんどくさいし、エラーを発生させやすいです。もし呼び出しの順番を間違えたり、呼び出しを抜かしてしまったりするとアプリケーションが正常に動かなくなります。
それに、それぞれのリクエストごとにエラーハンドリングをしなければならないのでコードも複雑になり、保守性が低くなります。
この処理を一つの塊としてインターフェースに定義することで、インターフェースが実際のユースケースに沿ったコントラクトの宣言を行うので、より明確なインターフェースになります。
さらにエラーハンドリングがコンポーネントに隠蔽されることで、トランザクション処理のようなメソッドデザインにすることができます。
What's next?
- Iterator Pattern
- Enumeration Method Pattern
- Batch Method Pattern
それぞれのデザインパターンについて理論だけじゃなくて、コードでもイメージを掴みたい