1. yamabico

    Posted

    yamabico
Changes in title
+EffectiveJava読書会3日目 Part.1
Changes in tags
Changes in body
Source | HTML | Preview

2日目(第3章)の記事はこちら → EffectiveJava読書会2日目

第4章 クラスとインターフェース

項目13  クラスとメンバーへのアクセス可能性を最小限にする

  • 最低限のpublicのAPIを設計し、関係のないクラス・インターフェース・メンバーがAPIの一部となるのを防ぐ。

  • 設計として各モジュール(機能)が独立し、それぞれの内部データ・実装の詳細が隠蔽され、実装とAPIがはっきりと分離されていることが重要
    ->オブジェクト指向プログラミング言語であるJAVAにとって大切な概念の一つである カプセル化

その利点

・開発・テスト・修正・保守が容易
・並行してモジュール開発が可能
・他に大きな影響を与えることなく最適化
等々

それを実現するために・・・

  1. トップレベルのクラスのアクセスレベルの考える   ->publicである必要なクラスはないか
  2. 基本的にはメンバー(フィールド・メソッド・ネストクラス等)はprivateに
  3. テストの容易化とアクセスレベル 
  4. publicな可変のインスタンスフィールドを持つクラスはスレッドセーフではない
  5. 4の例外として 不変オブジェクトの 定数はOK
  6. 可変長オブジェクト(配列など)のpublic static final(定数)のフィールドを返すアクセッサーメソッドは誤り
_4_13.java
// Potential security hole!
public static final Thing[] VALUES = { ... };

//Good pattern1
private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES =
 Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

//Good patern2
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}

項目14  publicのクラスでは、publicのフィールドではなくアクセッサ―メソッドを使う

publicなクラスでは決して可変のフィールドを公開にせず、privateにし、アクセッサーメソッド(getter)・ミューテーター(setter)を提供すべき

以上。

項目15  可変性を最小限にする

  • クラスは可変にすべきかなり正当な理由がない限り、不変であるべき
  • 不変にできないのであれば、その可変性を出来るだけ制限すべき
  • やむを得ない理由が無い限り、全てのフィールドをfinalにする
不変にする利点
  • 不変オブジェクトは本質的にスレッドセーフで同期を必要としない ->防御コピーやコピーコンストラクタを用意する必要がない
  • オブジェクトの存在できる状態を減らすことで、オブジェクトについて明確に論理的に考える容易になり、エラーの可能性も減る
不変クラスを作る5規則
  1. オブジェクトの状態を変更するためのメソッド(ミューテーター)を提供しない
  2. クラスが拡張できないことを保証する
  3. すべてのフィールドをfinalにする
  4. すべてのフィールドをprivateにする
  5. 可変コンポーネントに対する独占的アクセスを保証する
不変クラスの実装方法について

戻り値として新たなインスタンスを戻り値とする

_4_15_1.ajva
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
// Accessors with no corresponding mutators
public double realPart() { return re; }
public double imaginaryPart() { return im; }
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
              :

->オペランドを変更させない 関数的方法

サブクラスを防ぐ方法について
  1. finalをつける
  2. コンストラクタをprivate + static ファクトリーメソッドを使う
_4_15.java
public class Complex {
private final double re;
private final double im;
private Complex(double re, double im) {
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
... // Remainder unchanged
}

項目16  継承よりコンポジションを選ぶ

  • 継承は強力な力を持つため、 カプセル化 を破ってしまう恐れがある
  • 継承を使用して良いパターンの見極めることが大事
継承を進めない理由**
  • クラスの拡張(継承)はスーパークラスの実装に依存
  • 上記の理由から、スーパークラスの実装が変われば、サブクラスも変えなければいけない。 ->当初想定していない挙動やバグを生む危険がある

悪い例

BadInstrumentedSet.java
// Broken - Inappropriate use of inheritance!
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    public InstrumentedHashSet() {
    }
    public InstrumentedHashSet(int initCap, float loadFactor) {
    super(initCap, loadFactor);
    }
    @Override public boolean add(E e) {
    addCount++;
    return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
    addCount += c.size();
    return super.addAll(c);
    }
    public int getAddCount() {
    return addCount;
  }
}
InstrumentedHashSet<String> s =
new InstrumentedHashSet<String>();
s.addAll(Arrays.asList("Snap", "Crackle", "Pop"));
コンポジションのススメ

コンポジションとは・・・
既存クラスの拡張の変わりに既存クラスのインスタンスをprivateフィールドを持つ新たなクラスを作る
** コンポジションを進めるワケ**
既存クラスの実装の詳細に依存しないため、既存クラスの変更に影響されない

例)
転送クラス

ForwardingSet.java
// Reusable forwarding class
public class ForwardingSet<E> implements Set<E> {
 private final Set<E> s;
 public ForwardingSet(Set<E> s) { this.s = s; }
              :
 public boolean isEmpty() { return s.isEmpty(); }
 public int size() { return s.size(); }
 public Iterator<E> iterator() { return s.iterator(); }
 public boolean add(E e) { return s.add(e); }
 public boolean remove(Object o) { return s.remove(o); }
              :
 public boolean addAll(Collection<? extends E> c)
 { return s.addAll(c); }
              :
}

転送クラスのラッパークラス

InstrumentedSet.java
public class InstrumentedSet<E> extends ForwardingSet<E> {
 private int addCount = 0;
 public InstrumentedSet(Set<E> s) {
 super(s);
 }
 @Override public boolean add(E e) {
 addCount++;
 return super.add(e);
 }
 @Override public boolean addAll(Collection<? extends E> c) {
 addCount += c.size();
 return super.addAll(c);
 }
 public int getAddCount() {
 return addCount;
 }
}

使用例

Set<Date> s = new InstrumentedSet<Date>(new TreeSet<Date>(cmp));
Set<E> s2 = new InstrumentedSet<E>(new HashSet<E>(capacity));
継承はどこで使うのか

継承元クラスと継承したクラスの関係が、本当のサブタイプ関係である時のみ使うべき
後で追記する

参考
http://jp.fujitsu.com/solutions/sdas/technology/java-oo/05-object-composition.html
http://www.itsenka.com/contents/development/java/inheritance.html#S03

項目17  継承のために設計および文章化する、でなければ継承を禁止する

項目16の内容をも受け・・

クラスはオーバーライド可能なメソッドの自己利用を文書化しなければいけない。
  • オーバーライド可能なメソッドがどのように呼び出されるのか
  • 各呼び出しの後の処理にどのように影響するのか

例としてP.86の
java.util.AbstractCollection#remove

継承のために設計されたクラスのサブクラスを書きをテストする
  • サブクラスのテストを通して何を公開(protected)メソッド・フィールドにするか判断する
  • テストはスーパークラスの作成者以外に書いてもらうべき

-> 汎用的に使用されそうなクラスを作った場合、安易にメソッドやフィールドの実装を決定すると後々のパフォーマンス・機能改善を不可能にするので、上記のテストはすべき

コンストラクタ内でオーバーライド可能なメソッドは呼ばない

サブクラスで正しい挙動にならず、プログラムが失敗する原因となる

例)
2度日付が表示されることを想定しているが、スーパークラスのコンストラクタは
サブクラスのコンストラクタより先に実行されるため、初回時nullとなってしまう。

Super.java
public class Super {
// Broken - constructor invokes an overridable method
public Super() {
overrideMe();
}
public void overrideMe() {
}
}
Sub.java
public final class Sub extends Super {
private final Date date; // Blank final, set by constructor
Sub() {
date = new Date();
}
// Overriding method invoked by superclass constructor
@Override public void overrideMe() {
System.out.println(date);
}
public static void main(String[] args) {
Sub sub = new Sub();
sub.overrideMe();
}
}

以上から

  • 継承のために安全なクラスを設計すること->かなりの制限を課す
  • 安全にサブクラス化されるための設計と文書化がされてないクラスでのサブクラスは禁止

18章へ続く