前回の「オブジェクト指向について語った時に使ったメモ」に続いて、今度はインターフェースについて話しました。
用語や表現が違うだけで結局オブジェクト指向の時と同じことを話していないかと思ったなら、たぶんその直観は正しいのだと思います。自分でもそう思います。
前回も少し言いましたが、「オブジェクト指向」という用語には、過剰な思い入れや大げさな物言いがまわりにまとわりすぎて、快く思わない部分があります。オブジェクト指向設計ぐらいなら許せますが、オブジェクト指向を認知論や世界観と結びつけるような説明は誇大広告めいて好きではありません。
それに比べるとインターフェースは巨大な対象を分割統治していく時の武器として実にわかりやすく、素直に向き合えます。
20代の頃、Lotus Notesの数百万行のコードと格闘していました。デバッガで追えば細部は理解できますし、バグも直せました。しかしいくらコードを読み進めても、全体像をつかむには程遠い感じで、途方に暮れていました。
ある日、何がきっかけだったのか忘れましたが、境界を意識してコードの全体像をつかめばいいのだと啓示を得ました。
当時からメモを書きながらコードを読んでいましたが、啓示以降、メモの取り方が変わりました。それまでは関数や手続きの中身を読んで、その処理内容をメモる日々でした。啓示以降、コードを部品でとらえて、その部品が外部からどう見えるか(=どう使われるか)のメモになりました。
そんなこと(境界を意識したコードリーディング)は当時から知っている人には常識だったのかもしれませんが、不勉強なため実地の中で会得するしかありませんでした。このせいでだいぶ無駄な時間も過ごしたので、新しい人は手っ取り早く感覚を身につけて、次のフェーズに飛んでほしいと思っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
インターフェースと抽象化 説明のための用語使い分け - 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: フレームワークプログラミングで暗黙に実施していた技法を明確化。オブジェクト構築の役割を分離。オブジェクトは渡してもらうものという発想の転換。 |
最近のコメント