はじめに
Javaのオブジェクトの生成方法の選択基準がわかりづらい初級者(自分も含め)が多いと感じたのでまとめ。
主に以下を参考にまとめています。
Effective Java 第3版
第2章オブジェクトの生成と消滅
項目1 コンストラクタの代わりにStaticファクトリメソッドを検討する
項目2 多くのコンストラクタパラメータに直面した時にはビルダーを検討する
この記事で学ぶ内容
- Javaにおけるオブジェクト生成方法の種類
- Javaにおけるオブジェクト生成方法の選択基準
- 対象パターン->
Staticファクトリ
ビルダー
コンストラクタ
JavaBeans(*厳密には生成方法ではないですが)
結論:採用基準まとめ
最初に本記事の目的である各生成方法の採用基準(つまり結論)を書きます。
個人的にはほとんどのケースでStaticファクトリとビルダーを第一候補として採用していく予定です。
以下ケース別にまとめます。
・ シンプルなケース :
パラメータが少なく、名前付きで簡単にオブジェクトを生成可能。
& 柔軟性と拡張性が必要な場合(レアケース※別記事で取り上げる予定)。
→ Staticファクトリ
// メソッド名をつけることで生成意図が表現できる。
HamburgerByStaticFactory hamburgerWithCheese = HamburgerByStaticFactory.withCheese("チキン", "バケット", "ゴーダチーズ");
HamburgerByStaticFactory hamburgerWithCheeseAndSauce = HamburgerByStaticFactory.withCheeseAndSaouce("チキン", "バケット", "コーダチーズ", "マヨネーズ");
・ 複雑なケース :
パラメータが多い or オプションが多い。
→ ビルダー
// メソッドチェーンで各パラメータ名を表現できる。
HamburgerByBuilder hamburger = new HamburgerByBuilder.Builder("ビーフ", "小麦バン").cheese("スイスチーズ").saouce("バーベキュー").build();
・ 非常に単純なケース :
オプション無し and クラスの目的が明確
→ コンストラクタ
// シンプル
HamburgerByConstructor hamburger = new HamburgerByConstructor("ビーフ", "小麦バン", "アメリカンチーズ", "マヨネーズ");
・ ほぼ使用しない :
動的な設定が必要な場合のみ
→ JavaBeans
// 動的な設定可能 しかし 不整合オブジェクトになる可能性あり&可変性が伴う。
HamburgerByJavaBeans hamburger = new HamburgerByJavaBeans();
hamburger.setMeetType("ビーフ");
hamburger.setBunType("麦バン");
hamburger.setCheese("アメリカンソース");
hamburger.setSaouce("マヨネーズ");
オブジェクト生成パターン解説
Staticファクトリ
長所
・名前付きのメソッド:
インスタンス生成の目的を名前で表現できる。
短所
・管理の困難性:
オプションが多い場合、メソッドが増えて管理が困難になる。
実装例
public class HamburgerByStaticFactory {
private final String meetType;
private final String bunType;
private final String cheese;
private final String saouce;
// プライベートコンストラクタ
private HamburgerByStaticFactory(String meetType, String bunType, String cheese, String saouce) {
this.meetType = meetType;
this.bunType = bunType;
this.cheese = cheese;
this.saouce = saouce;
}
// 生成意図ごとに名前付けし、ファクトリメソッドを設定
// オプションのチーズをつける。
public static HamburgerByStaticFactory withCheese(String meetType, String bunType, String cheese) {
return new HamburgerByStaticFactory(meetType, bunType, cheese, "None");
}
// オプションのソースをつける。
public static HamburgerByStaticFactory withSaouce(String meetType, String bunType, String saouce) {
return new HamburgerByStaticFactory(meetType, bunType, "None", saouce);
}
// オプションのチーズ・ソース両方つける。
public static HamburgerByStaticFactory withCheeseAndSaouce(String meetType, String bunType, String cheese, String saouce) {
return new HamburgerByStaticFactory(meetType, bunType, cheese, saouce);
}
}
// メソッド名をつけることで生成意図が表現できる。
HamburgerByStaticFactory hamburgerWithCheese = HamburgerByStaticFactory.withCheese("チキン", "バケット", "ゴーダチーズ");
HamburgerByStaticFactory hamburgerWithCheeseAndSauce = HamburgerByStaticFactory.withCheeseAndSaouce("チキン", "バケット", "コーダチーズ", "マヨネーズ");
ビルダー
長所
・メソッドチェーン:
オプションが多い場合でも、メソッドチェーンを用いてシンプルにインスタンスを生成可能。
短所
・実装の複雑性:
実装方法が少し複雑。
実装例
public class HamburgerByBuilder {
private final String meetType;
private final String bunType;
private final String cheese;
private final String saouce;
// 内部クラスのビルダーを引数に渡す。
private HamburgerByBuilder(Builder builder) {
// 内部クラスのBuilderからフィールドの値を代入。
meetType = builder.meetType;
bunType = builder.bunType;
cheese = builder.cheese;
saouce = builder.saouce;
}
// 内部クラスとしてビルダークラスを作成。
public static class Builder {
// 必須項目
private final String meetType;
private final String bunType;
// オプション項目: デフォルト値を設定。
private String cheese = "None";
private String saouce = "None";
// 必須項目のみのコンストラクタを作成
public Builder(String meetType, String bunType) {
this.meetType = meetType;
this.bunType = bunType;
}
// オプションを設定するメソッドの設定。
// Builderを戻り値とすることでメソッドチェーンを可能にする。
public Builder cheese(String val) {
cheese = val;
return this;
}
public Builder saouce(String val) {
saouce = val;
return this;
}
// メインクラスにthis(=ビルダークラス自身)を渡してメインオブジェクトを生成する。
public HamburgerByBuilder build() {
return new HamburgerByBuilder(this);
}
}
}
// メソッドチェーンで各パラメータ名を表現できる。
HamburgerByBuilder hamburger = new HamburgerByBuilder.Builder("ビーフ", "小麦バン").cheese("スイスチーズ").saouce("バーベキュー").build();
コンストラクタ
長所
・シンプル:
インスタンス生成が直感的でシンプル。
※必ず全員が理解できるというメリットは意外と大きい。
短所
・不明瞭なパラメータ:
コンストラクタに多くのパラメータがあると、何を指しているのかわかりにくくなる。
・コンストラクタ実装の複雑性(テレスコーピングコンストラクタパターン):
オプションが多い場合、オーバーロードによるコンストラクタ実装が複雑になり可読性が落ちる。
実装例
public class HamburgerByConstructor {
private final String meetType;
private final String bunType;
private final String cheese;
private final String saouce;
// オプションが存在する場合は、コンストラクタをオーバーロードして複数作成する。
public HamburgerByConstructor(String meetType, String bunType, String cheese, String saouce) {
this.meetType = meetType;
this.bunType = bunType;
this.cheese = cheese;
this.saouce = saouce;
}
}
// シンプル
HamburgerByConstructor hamburger = new HamburgerByConstructor("ビーフ", "小麦バン", "アメリカンチーズ", "マヨネーズ");
JavaBeans
※厳密には生成方法ではなくコンストラクタのフィールド定義方法になります。
長所
・柔軟性:
オブジェクトの状態を後から簡単に変更できる。
短所
・不整合な状態:
オブジェクトが一時的に不完全な状態になる可能性がある。
※setterの実行が漏れると、該当のパラメータがnullになってしまう。
・不変性の欠如:
不変オブジェクトを作成することが困難。
実装例
public class HamburgerByJavaBeans {
private String meetType;
private String bunType;
private String cheese;
private String saouce;
public HamburgerByJavaBeans() {
}
// セッターの生成->これによりオブジェクトの不変性がなくなってしまう。ゲッターは割愛。
public void setMeetType(String meetType) {
this.meetType = meetType;
}
public void setBunType(String bunType) {
this.bunType = bunType;
}
public void setCheese(String cheese) {
this.cheese = cheese;
}
public void setSaouce(String saouce) {
this.saouce = saouce;
}
}
まとめ
(チームのレベルに合わせて生成方法は選択するべきという前提はあるが)
個人的には意図と実装の分離
を実現するために、積極的にStaticファクトリとビルダーを採用していきたいと考えています。
今後方針が変更となった場合は本記事で追記していきます。
サンプルコード(GitHub)
参考文献
- Effective Java 第3版
- Java言語で学ぶデザインパターン入門第3版
- Head Firstデザインパターン 第2版
お願い
記事の内容に誤り、もしくはご意見ありましたらぜひご指摘いただけますと嬉しいです。
迅速に修正対応します。