はじめに
こんにちは、Gakken LEAP のエンジニアの okamoto です。
「良いコード/悪いコードで学ぶ設計入門」を読んで、「値オブジェクト」と「完全コンストラクタ」という設計パターンの組み合わせについての内容が印象に残ったため、個人的に調べた内容を含め簡単にまとめました。
値オブジェクトとは
値オブジェクトは、ただの値に「意味」や「ルール」を持たせたクラスのことです。
例えば、int
型の値に対して変数名によって「金額」や「年齢」のような意味を持たせますが、これらが本来持っている「負の値が存在しない」といった固有のルールに対応させることはできません。
また、この値を引数として扱いたい場合、int
型のままで値を受け取ろうとしてあると、所持金額を受け取ろうとしているところに値段を渡しても処理が通ってしまうことになります。
そんなときに値オブジェクトを使うと、各値に名前だけでない意味を持たせた上で安全に扱うことができます。
// 所持金クラス
public class PossessionMoney {
private final int value;
public PossessionMoney(int value) {
if (value < 0) {
throw new IllegalArgumentException("所持金は0以上である必要があります");
}
this.value = value;
}
public int getValue() {
return value;
}
}
// 値段クラス
public class Price {
private final int value;
public Price(int value) {
if (value <= 0) {
throw new IllegalArgumentException("値段は1円以上である必要があります");
}
this.value = value;
}
public int getValue() {
return value;
}
}
完全コンストラクタ
完全コンストラクタとは、クラスのインスタンスを作るときに必要な情報をすべて受け取り、不正な値や未初期化を防ぐための仕組みです。
インスタンス変数を全て初期化できるコンストラクタを作成し、そのコンストラクタ内で不正値をはじくことができるようなチェックを行うことで、インスタンス生成時に不正な状態となることを確実に防ぐことができます。
// 値オブジェクトを利用した完全コストラクタを持つクラス
public class Purchase {
private final PossessionMoney possession;
private final Price price;
// intではなく各クラスの型で受け取る
public Purchase(PossessionMoney possession, Price price) {
if (possession == null || price == null) {
throw new IllegalArgumentException("金額情報が不足しています");
}
this.possession = possession;
this.price = price;
}
}
値オブジェクトと完全コンストラクタ
この2つのパターンを組み合わせることで「クラスの中身は安全に守られていて、間違った使い方がされにくい」状態となりカプセル化が実現されます。
特に、「意味のある値」や「守るべきルール」を明確にコード上に表現できるため、後からコードを読む人にも意図が伝わりやすくなります。
おわりに
本記事では、値オブジェクトと完全コンストラクタという2つの設計パターンを紹介しました。
組み合わせることで「正しい状態しか持たないクラス」を作ることができ、結果として、安全で意図が明確なコードにつながります。
こういった手法を少し取り入れるだけでもコードの質が向上するので試してみる価値があるかと思います。
参考
良いコード/悪いコードで学ぶ設計入門
【設計パターン入門】値オブジェクトと完全コンストラクタについての個人的まとめ
エンジニア募集
Gakken LEAP では教育をアップデートしていきたいエンジニアを絶賛大募集しています!
ぜひお気軽にカジュアル面談へお越しください!
https://gakken-leap.co.jp/recruit/