感謝のプログラミング 10000時間

たどり着いた結果(さき)は、感謝でした。

Builderパターンについて考えてみる。

スポンサーリンク

Effective Javaの11p〜16p 項目2「数多くのコンストラクタパラメータに直面した時にはビルダーを健闘する」について。

コンストラクタに多くの引数を渡さなければオブジェクトを作成できないときにどうするか、を考える。

伝統的には「テレスコーピングコンストラクタ」というパターンを使用してきた。
テレスコーピングコンストラクタというのは、同じ名前のコンストラクタを複数用意するパターンで、必須パラメータだけのもの、必須パラメータ+オプションパラメータのものなど、複数のコンストラクタを用意する。
こんな感じに。

public Friend(String name, int age) {
	this(name, age, 0, "nothing");
}

public Friend(String name, int age, int weight) {
	this(name, age, weight, "nothing");
}

public Friend(String name, int age, int weight, String hobby) {
	this.name = name;
	this.age = age;
	this.weight = weeight;
	this.hobby = hobby;	
}

このテレスコーピングコンストラクタパターンは、パラメータが多くある場合はコードを書くのが困難になって、しかも可読性が下がってしまう。

その他には、JavaBeansパターンといって、セッターを通じてパラメータを設定するパターンがあるが、このパターンだと生成途中の中途半端なオブジェクトを使用してしまう可能性がある。
そこで登場するのがビルダーパターンだ。

ビルダーパターンというのは、直接ほしいオブジェクトを生成するのではなく、
必須パラメータをすべてもつコンストラクタを呼び出して、ビルダーオブジェクトを得る。
ビルダーーオブジェクトのセッターを呼び出してパラメータを設定する。
で、buildメソッドを呼び出してオブジェクトを生成する。

これだけじゃよくわからないので、本を真似して作ってみる。
ビルダーパターンを使ったクラス。

package effective.chap2;

public class Friend {
    private final String name;
    private final int age;
    private final int weight;
    private final String hobby;
    
    public static class Builder {
        //必須パラメータ
        private final String name;
        private final int age;
        private final int weight;
        
        //オプションパラメータ
        private String hobby;
        
        public Builder(String name, int age, int weight) {
            this.name = name;
            this.age = age;
            this.weight = weight;
        }
        
        public Builder hobby(String val) {
            hobby = val;
            return this;
        }
        
        public Friend build() {
            return new Friend(this);
        }
        
    }
    
    private Friend(Builder builder) {
        name = builder.name;
        age = builder.age;
        weight = builder.weight;
        hobby = builder.hobby;
        checkParameter();
    }
    
    private void checkParameter() {
        if (this.age < 0) {
            throw new IllegalArgumentException("age is minus:" + this.age);
        }
        if (this.weight < 0) {
            throw new IllegalArgumentException("weight is minus:" + this.age);
        }
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getWeight() {
        return weight;
    }

    public String getHobby() {
        return hobby;
    }
        
}

で、これを使う側。
この使う側のコードが読みやすく、使いやすくなっているのがポイントみたい。

package effective.chap2;

public class Main {
    public static void main(String[] args) {
        Friend sho322 =new Friend.Builder("sho322", 28, 180).hobby("Java").build();
        System.out.println(sho322.getName());
        System.out.println(sho322.getAge());
        System.out.println(sho322.getWeight());
        System.out.println(sho322.getHobby());
    }
}

実行結果はこうなる。

sho322
28
180
Java

これで、引数に不正な値(例えば、年齢パラメータにマイナスの値を入れるなど)を入れた時は、IllegalArgumentExceptionで例外を投げるようにする。

package effective.chap2;

public class Main {
    public static void main(String[] args) {
        Friend sho322 =new Friend.Builder("sho322", -1, 180).hobby("Java").build();
        System.out.println(sho322.getName());
        System.out.println(sho322.getAge());
        System.out.println(sho322.getWeight());
        System.out.println(sho322.getHobby());
    }
}

結果はこんな感じ。

Exception in thread "main" java.lang.IllegalArgumentException: age is minus:-1
	at effective.chap2.Friend.checkParameter(Friend.java:45)
	at effective.chap2.Friend.<init>(Friend.java:40)
	at effective.chap2.Friend.<init>(Friend.java:35)
	at effective.chap2.Friend$Builder.build(Friend.java:30)
	at effective.chap2.Main.main(Main.java:5)

例外がthrowされた。
たしかに、Builderパターンを使えば、複数のパラメータを、可読性を保ったまま設定することができる気がする。

Friend sho322 =new Friend.Builder("sho322", 28, 180).hobby("Java").build();

こんな感じで。

勉強した本

Effective Java 第2版 (The Java Series)

Effective Java 第2版 (The Java Series)

感謝のプログラミング

今回で感謝のプログラミングは【593時間目】
10000時間まで、あと【9407時間】