Effective java 輪読会 第4章 項目18-22
-
Upload
appresso-engineering-team -
Category
Documents
-
view
664 -
download
5
Transcript of Effective java 輪読会 第4章 項目18-22
Effective Java 輪読会 第 3回(項目 18 ~ 22 )2013/12/18
開発部 野口
項目 18抽象クラスよりインタフェースを選ぶ
抽象クラスとインタフェース 抽象クラス
実装を含むことができる サブクラス化が必要
Java は単一継承のみを許可している インタフェース
実装を含むことができない サブクラスである必要がない
インタフェースの利点( 1/3 ) 新たに実装することが容易
型階層に影響を及ぼさないため 抽象クラスの場合、型階層に組み入れる必要がある
ミックスインを定義するのに理想的 クラスが「本来の型」に加えて、なんらかの任意
の振る舞いを提供していることを宣言するために、クラスが実装する型 例) Comparable
インタフェースの利点( 2/3 ) 階層を持たない型フレームワークを構築でき
る 例) pp.91-92 SingerSongwriter
抽象クラスで行うと、組み合わせ爆発が起こる! 安全で強力な機能エンハンスを可能にする
ラッパークラスによって、継承しなくてもエンハンスできる 例)項目 16 InstrumentedSet
インタフェースの利点( 3/3 ) 抽象骨格実装クラスを提供することで、抽象
クラスの長所も取り入れることができる 例)主要なコレクションインタフェース
具象実装の例 : pp. 92-93 intArrayAsList() 擬似多重継承を用いることもできる
ラッパークラスの応用形
抽象クラスの利点 発展させるのが容易
たとえば、抽象クラスに具象メソッドを追加するだけで、すべてのサブクラスがそのメソッドを提供することになる インタフェースでは、そうはいかない。追加した時点
でコンパイルエラーになる インタフェースは、凍結前にできるだけ(多くのプロ
グラマが)実装することでテストすべき
まとめ( 1/2 ) インタフェースは、一般的に複数の実装を許
す型を定義する最善の方法 発展しやすさが、柔軟性と能力よりもより重
要だと考えられている場合は例外 そのような場合は、抽象クラスを使用する
まとめ( 2/2 ) 重要なインタフェースを提供するときは、骨
格実装の提供を検討しよう public インタフェースは細心の注意を払って
設計し、複数の実装を書くことで徹底的にテストしよう
項目 19 型を定義するためだけにインタフェースを使用する
インタフェースの目的 インタフェースを実装するクラスのインスタ
ンスを参照するために使用できる型として機能すること
実装するクラスのクライアントは何ができるかを述べること
他の目的は、不適切
不適切なインタフェースの例 定数インタフェース
「定数インタフェースパターンは、インタフェースの下手な使い方」 実装の詳細であり、クラスのユーザには何の意味もな
いから (本来は不要な)負うべき義務を表しているから
定数が必要なくなっても、バイナリ互換性のためにそのインタフェースの実装が必要
疑問 : 「バイナリ互換性のため」というのは、どういう意味……?
→定数インタフェースを implements しているクラスに何かしら影響がある
定数インタフェースを避ける その定数が結びついたクラスやインタフェー
ス自体に定数を追加する 例) Integer.MIN_VALUE や
Integer.MAX_VALUE 列挙されるものなら、 enum を使用する 定数インタフェースよりは、ユーティリティクラ
スを使用する static インポートを活用すれば、長い修飾をするこ
とも回避できる
項目 20タグ付クラスよりクラス階層を選ぶ
タグ付クラス 振る舞いを切り替えるためのタグを( enum
等のかたちで)メンバに持ち、そのメンバの設定値に応じて各種操作に対する振る舞いを switch 文で切り替えるようなクラス 例) pp.98 Figure クラス
タグ付クラスは、冗長で、誤りやすく、非効率( 1/2 )
enum 宣言、タグフィールド、 switch 文といったお決まりのコードで散らかっている
複数の実装が単一クラスに詰め込まれ、読みづらい
他の特性に属する関係のないフィールドによって、メモリ量が増加する
フィールドを自然に final にできない フィールド初期化の正しさについて、コンパ
イラの助けを得られない
タグ付クラスは、冗長で、誤りやすく、非効率( 2/2 )
特性の追加に際して、ソースの修正が必要 OCP ( Open-Closed Principle / 開放 -閉鎖の原則)違反!
特性の追加に際して、すべての switch 文へ漏れなく case 追加が必要
型から特性に関する手がかりが得られない
かわりにクラス階層を選ぼう さっきのデメリット全部解消できます
タグ付クラスからクラス階層へ変換するためのざっくりした 3 ステップ
抽象クラスを定義する 特性ごとに具象サブクラスを定義する 抽象メソッドを個々の具象サブクラスで実装
する 例) pp.99 Figure / Circle / Rectangle クラ
ス
See Also:
『リファクタリング』( Martin Fowler ) サブクラスによるタイプコードの置き換え State/Strategy によるタイプコードの置き換え ポリモーフィズムによる条件記述の置き換え
項目 21 戦略を表現するために関数オブジェクトを使用する
関数をパラメータ化する様々な機構
関数ポインタ( C とか) デリゲート( C# とか) ラムダ式( Lisp とか)
関数オブジェクト 他のオブジェクトに対して操作を行うメソッ
ドを 1 つだけ公開するオブジェクト 実質、そのメソッドへのポインタ
例: StringLengthComparator
文字列比較に対する具象戦略( concrete strategy ) 状態を持たない
よって、シングルトンにするとよい
戦略インタフェース 戦略を切り替えられるようにするために、具
象戦略が実装するインタフェース 例) Comparator<T>
無名クラスを使用して宣言するのもよい ただし、インスタンス生成コストに注意 名前がつけられないのもデメリットとなりうる
ホストクラス 具象戦略を private な内部クラスとして、イン
タフェースを通して外部に提供する 例) pp.103 Host クラス
*疑問 : pp.103 の StrLenCmp はなぜ(何のために) Serializable を実装しているのか?(単なる例示のため?だとしたら、あまり良い例ではないような……)→Comparator を Serialize して転送したいこともある!
まとめ 関数ポインタの主な使用方法は、 Strategy
パターンを実装すること Java での実装は、戦略インタフェースと、そ
れを実装する具象戦略クラス 具象戦略が 1度しか使用されない場合は、無名クラスを使用するとよい
繰り返し利用される場合は、ホストクラスを導入するとよい
補足( 1/2 ) Java8 ではラムダ式も使えるそうですね!
Before ( pp.102 ) :
Arrays.sort(stringArray, new Comparator<String>() { public int compare(String s1, String s2) { return s1.length() - s2.length(); } });
補足( 2/2 ) Java8 ではラムダ式も使えるそうですね!
すっきり!
参考 : http://dstn.appresso.com/developer/detail/?id=131 http://d.hatena.ne.jp/nowokay/20130824
After (ラムダ式による記述) :
Arrays.sort(stringArray, (s1, s2) -> s1.length() - s2.length());
項目 22 非 static のメンバークラスより static のメンバークラスを選ぶ
ネストしたクラス 他のクラス内に定義されたクラス エンクロージングクラスに対して仕えるためだ
けに存在すべき 他の何らかの状況で有用ならば、トップレベルの
クラスであるべき
4 種類のネストしたクラス static のメンバークラス 非 static のメンバークラス 無名クラス ローカルクラス
static のメンバークラス エンクロージングクラスのメンバーのすべてに
アクセスできる public のヘルパークラスとして提供すること
もできる 例) pp. 104 Calculator.Operation
非 static のメンバークラス メンバークラス生成時に、そのエンクロージン
グインスタンスと関連付けられる 関連付けの際にメモリを消費し、インスタン
スの生成時間が増加する アダプターの実装に用いることができる
例) Map インタフェースのkeySet 、 entrySet 、 values
非 static のメンバークラスよりstatic のメンバークラスを選ぶ
エンクロージングオブジェクトへの関係のない参照を持たない
ガーベッジコレクションへの悪影響を防ぐ public あるいは protected のメンバーの場
合は、あとから変更できないので特に慎重に選ぶこと
無名クラス( 1/2 ) 名前を持たない エンクロージングクラスのメンバーではない ( static の文脈内で書かれたとして
も) static のメンバーを持つことはできない 宣言された箇所以外でインスタンス化できな
い
無名クラス( 2/2 ) instanceof やクラスの名前を利用できない 複数のインタフェースを実装したりできない クライアントは、スーパータイプから継承され
たメソッド以外呼び出せない 短くあるべき( 10 行以下が目安)
無名クラスの用途 関数オブジェクトとして使用する
See also: 項目 21 プロセスオブジェクトを生成する
例) Runnable インスタンス、 TimerTask インスタンス等
static ファクトリーメソッド内で使用する 例)項目 18 の intArrayAsList
ローカルクラス ローカル変数が宣言できる場所すべてで宣言で
きる 無名クラスと同様、 static のメンバーを持て
ない 無名クラスと同様、短くあるべき
まとめ( 1/2 ) 4 種類のネストしたクラスがあり、それぞれ異
なる用途がある メソッド内に収まらないようなものは、メン
バークラスにする エンクロージングインスタンスへの参照が必要
なら、非 static にする そうでなければ、 static にする
まとめ( 2/2 ) メソッド内に属しているべきで、 1箇所から
のみ生成され、ベースとなる型が存在するなら、無名クラスにする そうでなければ、ローカルクラスにする