IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Effective Java 読書会 4 日目「最近はあまり継承とか使わないらしい」

はじめに

らんらーらららんらんらーん♪(なうしか)っと
( ´ー`)フゥー...(゚Д゚)ハッ!

嘘です><ちょっと言ってみたかっただけです><

今日読んだところ

67 ページ〜 85 ページ

情報隠蔽カプセル化

API 以外の情報をモジュール外から見えなくすること
余計なテストとか書かなくていいから楽だよね。

パッケージプライベート

同じ jar の同じパッケージからのみ参照できる

protected, public

完全公開 API として保守していかなければならないよ。
private とパッケージプライベートとは全然違うレベルよ

インスタンスフィールド

private にしる! 
public static final で、その参照先が不変クラスな場合に限り private じゃなくてもいい 
public static final で配列とか持たないようにね

ミューテーター

オブジェクトの状態を変更するようなメソッドをミューテーターっていうんだね。

不変クラス 5 条件

  • ミューテーターを持たない
  • クラスを final にする
  • すべてのフィールドを final にする
  • すべてのフィールドを private にする
  • 他のオブジェクトと可変オブジェクトを共有しない。(防御的コピーを使って)

不変クラスの利点

  • 作成したときの状態から変化することがないので。誰とその変数を共有しているか考えなくていい
    • たとえば、「この関数に渡したら、この関数の中で値が変わらないかなー」とか考えなくてもいい
    • がんがん共有できて気持ちいいい
  • 本質的にスレッドセーフ
    • 値が変わらないので、同期をとる必要がない。
  • ハッシュが生成時に決まる

不変クラスの生成

不変クラスは private コンストラクタ, static ファクトリーメソッドと相性がいい。
生成時に値を決めなきゃいけないので、 static ファクトリーメソッドでいろいろなファクトリーを作るといい

可変クラス作るな!

よっぽど正当な理由がないかぎり、不変クラスにしる!
セッターを作りたい衝動は、禁止です><

継承よりコンポジション

継承は、スーパークラスの実装を詳細に理解していないとすることができない。
80 ページの例は、最初何故ダメなのか分からなかったが、分かってみると非常に勉強になった。

転送クラス

あるインタフェースにおける Proxy クラスを作るためのスーパークラス
転送クラスを使えば、簡単に Proxy クラスが作れる。

SELF 問題

Proxy パターンでは、 Proxy 経由で Real なクラスのメソッドが呼ばれることが想定されているが。
Real なクラスが自分の参照を含むオブジェクトを返す場合、そのオブジェクトは Real なクラスのメソッドを直接呼び出すことになる。
これを SELF 問題という

SELF 問題の例

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

// インターフェースがあって
interface AInterface {

    public ActionListener createListener();

    public void say();
}

// インターフェースを継承した実装
class A implements AInterface {

    // アクションリスナーを返す
    // このとき、アクションリスナーは this を参照している
    @Override
    public ActionListener createListener() {
        return new Listener(this);
    }

    // インスタンスメソッドの実装
    @Override
    public void say() {
        System.out.println("I'm A.");
    }
}

// クラス A の Proxy
class AProxy implements AInterface {

    private AInterface impl;
    
    public AProxy(A a) {
        impl = a;
    }

    @Override
    public ActionListener createListener() {
        return this.impl.createListener();
    }

    // say は Proxy で拡張する
    @Override
    public void say() {
        impl.say();
        System.out.println("But, I'm Proxy.");
    }
}

// アクションリスナーの実装
class Listener implements ActionListener {
    
    private AInterface parent;
    
    public Listener(AInterface parent) {
        this.parent = parent;
    }
    
    @Override
    public void actionPerformed(ActionEvent e) {
        parent.say();
    }
}

public class Main {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JButton button = new JButton("Click me");
        frame.getContentPane().add(button);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
        
        AInterface a = new AProxy(new A());

        // ここでボタンにアクションリスナーを登録
        button.addActionListener(a.createListener());
    }

}

このような場合にボタンがクリックされたときに AProxy の say は呼ばれず。 A の say が呼ばれてしまう。
これを SELF 問題という。

それでも継承するときは

すべての B が A であるという関係が成り立つことを念入りに確認すべし。
(A がスーパークラス、 B がサブクラス)

まとめ

週末も勉強するぞー!