インターフェースと抽象化
説明のための用語使い分け
- interface型: Javaの言語機能としてのインターフェース
- インターフェース: 特定の言語機能と独立した、プログラムの「境界」としてのインターフェース
そもそも
- Javaでinterface型をどう使うかは簡単
- 課題は、なぜ使うかといつ使うか
interface型を使う意義
- 抽象化 => 機能を限定、実装詳細の隠蔽で考えることを減らす
- 境界や契約を明示化 => 読み手の負担軽減
- 不変の意思表示 => 依存性の軽減
インターフェースの歴史(予備知識)
- Unixのファイルディスクリプタ
- 実装の隠蔽
- 機能の限定(抽象化)
- ファイルに対して、open、read、write、closeの基本4操作
=> 操作を限定すると考えることが減る(制約の利点)
=> cf. WebのREST、RDBのCRUD
=> 制約には、外れる例外も残る(lseek、ioctlなどのシステムコール)
- プロセス間通信(socket)もファイルのように扱える
- メモリ読み書きもファイルのように扱える
=> open、read、write、closeのように扱える「何か」は似たAPI設計にすれば学習コストが減る
=> 発達したAPIは文法のような共通認識になっていく
- API(システム境界)
- オブジェクト指向(メソッドの意味的なまとまりがインターフェース)
- リモートプロシジャ(RPC)
- 使う側と使われる側が一般的なAPIより疎結合になるので、規約(プロトコル)がより重要
- 分散オブジェクト
- 規格: CORBA、DCOM、SOAP、EJB
- 記述言語: IDL、WSDL
=> WebのRESTによるチープ革命が進行中
変数の型をinterface型をする判定基準
- 原理原則: interfac型は境界に使う
- 下記の順で検討の価値が高い
1. publicなメソッドの引数や返り値の型 => 使うことを検討
1.1. interface型から継承したメソッド
1.2. 抽象基底クラス型から継承したメソッド
1.3. クラス独自のメソッド
2. (もしあれば)publicなフィールドの型 => 使うことを検討(上記1に必要なら検討の必要性が上がる)
3. 非publicなメソッドの引数や返り値の型 => 上記1に必要なら検討の必要性が上がる
4. 非publicなフィールドの型 => 上記1に必要なら検討の必要性が上がる
5. ローカル変数の型 => 上記1に必要なら検討の必要性が上がる
- 補足
- 1.以外について => 1.のためにダウンキャストはしない。変数の型をinterface型にして1.を満たす
interface型を定義する判定基準
- どれだけ使われるか?
(= 使われる対象としての価値が高いか?)
- もっと明確な判定基準が欲しければ、メソッドの引数や返り値として引き回されることが多ければinterface型にする基準になる
- FAQ「実装クラスがひとつしかない時にinterface型を定義する意味はあるのか?」に対しては、上記の基準を満たせばあり、が回答
- 契約として強調したいか?
- 変わらない部分(変えたくない部分)をinterface型にする
- interface型への依存はOK、という使われる側から使う側への配慮
- 抽象化(=細部を切り捨て、処理や性質の共通性の切り出し)が適切にできるか?
interface型を定義する別の判定基準(井上の私見には合わない)
- 実装クラスが多数あるか?
- 個人的には、この判定基準はやや因果関係の逆転があると思う。上記の抽象化の帰結が先にあって、結果として成立する結果だと思う
- テスト容易性の観点で偽装オブジェクトの作りやすさの視点で、この判定基準もあり?
- 実装詳細を隠したいか?
- 個人的には、これは判定基準ではなく結果論だと思う(良いメソッド設計の指針としては正しいが、interface型導入の指針とは違う気がする)
補足:操作の限定(局所的な自由度の縮小)と受け入れ能力の自由度の拡大を混同しないように
(ローカル変数の型でinterface型を説明していると片手落ちになるので注意)
- ローカル変数の型を
CharSequence s = new StringBuilder()
にすると、変数sの参照先オブジェクトに対してできることが減る
=> 一見、自由度が下がるだけでたいしたメリットもないように見える
=> これだけ見せられると、その直感はほぼ正しい(ただし、このオブジェクトを色々と引き回すと事情は変わってくる)
- void method(CharSequence s)
のようにメソッドの引数の型をより上位型にする
=> このメソッドは、より多様なオブジェクトを受け入れる自由度を得られる
=> 究極まで自由度をあげると、変数の型がなくなる(動的型言語)
=> 制約と自由度のバランスが重要
抽象基底クラスとinterface型の継承の使い分けの基準
- 正解はないが、考えるのが面倒で、答えだけが欲しければ次の基準に従う
- テンプレートメソッドパターンの場合は抽象基底クラスの継承
- それ以外はinterface型の継承
補足: interface型、抽象基底クラス、具象クラスの3つ組
- 一時、このスタイルは流行った(気がする)
=> 一貫して同じスタイルを使って考えることを減らす利点は認めるが、個人的にはやりすぎと感じるのが多い(無理矢理、抽象基底クラスを使っているように感じることが多い)
=> 共通処理は別クラスにまとめて、処理を委譲するほうが簡単になる
発展的話題(<a href="http://www.amazon.co.jp/exec/obidos/ASIN/4774139904/arielnetworks-22/">「パーフェクトJava」</a>を参照)
- コールバック: 依存性の逆転。コールバックにする指針:変わりやすい部分を外に追い出す。変わりにくいほうへ依存させる
- ファクトリ: 具象クラスへの依存が残る最後の砦のオブジェクト生成の役割を分離
- DI: フレームワークプログラミングで暗黙に実施していた技法を明確化。オブジェクト構築の役割を分離。オブジェクトは渡してもらうものという発想の転換。
最近のコメント