constructorの役割は、「インスタンスが利用可能になるために必要な初期状態を作ること」に限定することが望ましい。
constructor に書くべきこと
constructorには、次のような内容を書くことが望ましい。
- クラスのインスタンスが初期状態として最低限必要とするプロパティの初期化や設定
- 依存性注入(Dependency Injection)として外部から渡されるオブジェクトや設定値を保持する処理
プロパティの初期化
クラスが正常に動作するためには、最低限必要な状態が設定されている必要がある。
そのため、引数で受け取った値やデフォルト値を用いて、必要最小限のプロパティを設定する。
class User {
private name: string;
private age: number;
constructor(name: string, age: number = 20) {
this.name = name;
this.age = age;
}
}
依存性の注入(DI)
外部のオブジェクトやサービスを受け取り、それをクラスの内部で保持することで、クラスの疎結合性を高め、テストを容易にすることができる。
class UserService {
constructor(private repository: UserRepository) {}
}
constructor に書くべきでないこと
constructorに以下のような処理を含めることは避けるべきである。
- 複雑なロジックやビジネスロジックの実行(これらは専用のメソッドに分離するべき)
- グローバルな状態の変更や副作用を伴う処理(設計として副作用を極力少なくすべき)
// 悪い例
class Config {
data: any;
constructor() {
fetch("config.json").then(res => res.json()).then(json => {
this.data = json;
});
}
}
// 良い例
class Config {
data: any;
private constructor(data: any) {
this.data = data;
}
static async create(): Promise<Config> {
const res = await fetch("config.json");
const data = await res.json();
return new Config(data);
}
}
複雑なビジネスロジックや条件分岐処理
constructorはインスタンス生成のための初期設定に特化するべきであるため、複雑なビジネスロジックや条件分岐を含めると、コードの読みやすさや保守性が著しく低下する。こうした処理は専用のメソッドやファクトリメソッドに切り出すことが望ましい。
// 悪い例
class Order {
constructor(items: Item[]) {
if(items.length === 0) throw new Error("Items required");
if(items.some(i => i.price <= 0)) throw new Error("Invalid item price");
this.totalPrice = items.reduce((sum, i) => sum + i.price, 0);
this.items = items;
}
}
// 良い例
class Order {
items: Item[];
totalPrice: number;
constructor(items: Item[]) {
this.items = items;
this.totalPrice = this.calculateTotalPrice();
}
private calculateTotalPrice(): number {
return this.items.reduce((sum, i) => sum + i.price, 0);
}
validate(): void {
if(this.items.length === 0) throw new Error("Items required");
if(this.items.some(i => i.price <= 0)) throw new Error("Invalid item price");
}
}
グローバルな状態変更や副作用のある処理
グローバルな状態をconstructor内で変更すると、テストやデバッグが困難になる。このような副作用はconstructorでは避け、constructorは純粋にインスタンス自身の初期設定に専念することが重要である。
// 悪い例
class UserSession {
constructor(user: User) {
SessionManager.setCurrentUser(user); // グローバルな状態変更
}
}
// 良い例
class UserSession {
user: User;
constructor(user: User) {
this.user = user;
}
activate(): void {
SessionManager.setCurrentUser(this.user);
}
}
謝辞
この記事は GPT に素朴な疑問を質問するやり取りを再編したものである。
また、この記事のドラフトを villagepump コミュニティ内でオープンパブリックに編集した際、誤りを bsahd 氏に指摘していただいた。ありがとう。