第13章:不変条件の配置:コンストラクタ/ファクトリで確定させる🏗️✅
この章でできるようになること🎯✨
- 「作れた=正しい状態」なクラス/型を作れるようになる🙆♀️✅
- 不変条件(Invariant)を 1か所に集めて 壊れにくくできる🧱✨
newを“直接触らせない”設計で、バグの入口を閉じられる🚪🔒
1. なんで「生成時に確定」させるの?🤔💡

不変条件(Invariant)を一番ラクに守る方法は、**「不完全な状態では絶対に生まれないようにする」**ことです🧱✨ だから理想はこれ👇
“存在しているインスタンスは、全部ルールを満たしている” ✅✅✅
これができると、コードがめちゃくちゃラクになるよ😆🌸 なぜなら…
- 関数の中で毎回「これ正しい値かな?」って疑わなくていい🕵️♀️❌
- 変な値が混ざって原因不明のバグになるのを防げる🧨🛑
- テストも「正しいものしか来ない前提」で書ける🧪✨
2. ありがちなダメ例(不変条件が散らばる)😵💫💥
たとえば「Emailはメールっぽい文字列である」が不変条件だとするね📩
❌ こうなると壊れやすい
new Email(" aaa@bbb.com ")が通る- どこかの関数で
trim()し忘れる - どこかの関数でチェックし忘れる
- 結果:“たまに壊れる” が発生😇💥
3. 王道パターン①:コンストラクタを隠して create() に集約🏭✨
3.1 形(結論)📌
-
constructorをprivate(またはprotected)にする🔒 -
生成は
static create()のみ🏭 -
create()の中で- 正規化(trim/toLowerなど)🧼
- 検証(ルール違反を止める)🛑
- インスタンス化(ここでだけ
new)🏗️
例:Email クラス(作れたら必ず正しい)📩✅
export class Email {
private constructor(private readonly value: string) {}
public static create(raw: string): Email {
const text = raw.trim().toLowerCase();
// 不変条件(Invariant)
if (text.length === 0) throw new Error("Emailは空にできません📩❌");
if (!text.includes("@")) throw new Error("Emailの形式が変です📩❌(@が必要)");
return new Email(text);
}
public toString(): string {
return this.value;
}
}
3.2 使う側がめっちゃシンプルになる😍✨
import { Email } from "./Email";
function sendWelcome(email: Email) {
// ここでは「Emailは正しい」前提でOK🙆♀️
console.log(`ようこそ! ${email.toString()} さん🎉`);
}
const email = Email.create(" Alice@Example.com ");
sendWelcome(email);
✅ ポイント
- Emailっぽくない文字列は
Email.create()の時点で止まる🛑 sendWelcome()は “正しいEmailが来る” だけ考えればOK🎯✨
4. 王道パターン②:プリミティブ型は「ブランド型+ファクトリ関数」🪪✨
「Emailは実体はstringなんだけど、ただのstringとは区別したい」って時あるよね🙂 そのとき便利なのが ブランド型(Nominalっぽくするやつ)✨
declare const emailBrand: unique symbol;
export type Email = string & { readonly [emailBrand]: "Email" };
export function createEmail(raw: string): Email {
const text = raw.trim().toLowerCase();
if (text.length === 0) throw new Error("Emailは空にできません📩❌");
if (!text.includes("@")) throw new Error("Emailの形式が変です📩❌(@が必要)");
return text as Email;
}
使い方(ただのstringと混ざらない)🧠🔒
import { Email, createEmail } from "./email";
function sendWelcome(email: Email) {
console.log(`ようこそ! ${email} さん🎉`);
}
const s = "bob@example.com";
// sendWelcome(s); // ❌ stringはEmailじゃない!
const email = createEmail(s);
sendWelcome(email); // ✅
✅ どっちを選ぶ?
- クラス型:振る舞い(メソッド)も一緒に持たせたいとき🧰
- ブランド型:軽く「ただの値」を守りたいとき🪶
5. new を隠すテクニック集🔒🛠️
「new できちゃう」状態を放置すると、だいたい後で壊れる😇💥
なので、次のどれかで封じるのが定番だよ✅
constructorをprivateにする(クラス)🔒classを export せず、create()だけ export する(モジュール境界)🚧- ブランド型+ファクトリ関数にする🪪
6. 不変条件チェック、どこまで厳密にする?📏🤔
不変条件は“強く”したいけど、やりすぎると辛いこともあるよね🙂💦
おすすめの考え方👇
-
軽いチェック(空・範囲・必須文字など)→ 不変条件として持ちやすい💪✨
-
重いチェック(外部API問い合わせ、DNSチェック等)→ 生成時にやると重くなりがち🐢💦
- こういうのは境界層でやる/別の検証ステップに分けるのが無難🚧
7. 演習🧪✍️(手を動かすと一気に身につく!)
演習1:NonEmptyString を作る🧵✅
- ルール:空文字・空白だけは禁止🙅♀️
- 形式:クラスでもブランド型でもOK✨
演習2:Percent(0〜100)を作る📊✅
- ルール:
0 <= p <= 100 create(120)は止める🛑
演習3:Email.create() をちょっと強くする📩💪
- ルール追加例:
@が先頭/末尾はダメ、@が複数はダメ…など🎯 - ただし「正規表現で完璧にやろう」としすぎないでOK(まずは安全第一)🧯✨
8. AI支援🤖💬(サクッと手伝ってもらう)
8.1 雛形を作ってもらう⚡
- 「TypeScriptで private constructor + static create のValueObject例を作って」
- 「ブランド型で Email と UserId を作る例を出して」
8.2 ルールのレビューをしてもらう🔎
- 「Emailの不変条件として最低限どこまで見るべき?」
- 「重い検証を不変条件に入れるデメリットを列挙して」
8.3 事故りやすい入力を出してもらう🧨
- 「Emailでバグりやすい入力例を20個出して(空白、全角、複数@など)」
9. 章末チェックリスト✅✨
- 不変条件を“1か所”に集められた🧱
-
newを外から触れない形にできた🔒 - 「作れた=正しい状態」になっている✅
- 使う側のコードがスッキリした(チェック地獄が消えた)😆✨
ちょい最新メモ🗞️✨(2026年1月時点)
- TypeScript の安定版は 5.9系が最新として配布されているよ📦✨(npmの
typescriptの Latest 表示) (npm) - TypeScript 5.9 のリリースノートでは、
tsc --initの更新や--module node20など、現行環境に合わせた改善がまとまってるよ🛠️ (TypeScript) - さらに先の動きとして、TypeScript をネイティブ実装へ寄せる TypeScript 7 native preview の話も出ていて、大規模コードでコンパイルが大幅に速くなる報告があるよ⚡(プレビュー段階) (Microsoft Developer)
まとめ✨🎀
不変条件は「後で守る」じゃなくて、“作る瞬間に確定” させるのが最強だよ🏗️✅
create()(またはファクトリ)に集めて、new を隠せば、コードが一気に壊れにくくなる🧱🔒✨