🏭第8章 実装を隠す(ファクトリーメソッド)
TODOアプリの実装が進むにつれて、重複したコードが見えてきました。 実装を隠蔽するファクトリーメソッドパターンを導入し、より洗練された設計を実現します。
「2つのサブクラス`Dollar`と`Franc`にはもうコンストラクタしか残っていない。 コンストラクタだけのサブクラスを残しておく理由はないので、サブクラスを消してしまいたい。」
TODOリスト
- •
`TodoItem`と`CompletedTodoItem`の重複
- •
`TodoItem`のサブクラスを隠す
- •
TypeScriptの型安全性を保つ
- • 実装を隠すファクトリーメソッド
🔍現在の実装の問題点
第7章までで、基本的なTODOアイテムのクラス構造ができました。 しかし、TodoItem
とCompletedTodoItem
の間に重複コードが存在し、 さらにクライアントコードが具体的なサブクラスを知りすぎています。
export class TodoItem {
constructor(
public id: number,
public text: string,
public completed: boolean = false
) {}
complete(): TodoItem {
return new CompletedTodoItem(this.id, this.text);
}
equals(other: TodoItem): boolean {
return this.id === other.id &&
this.text === other.text &&
this.completed === other.completed;
}
}
export class CompletedTodoItem extends TodoItem {
constructor(id: number, text: string) {
super(id, text, true);
}
complete(): TodoItem {
return this; // 既に完了済み
}
}
🔴1. レッド:Factory Methodを使うテストを書く
まず、TodoItem
クラスに Factory Methodを導入し、クライアントコードがサブクラスを直接知らなくて済むようにします。
describe('TodoItemファクトリーメソッドのテスト', () => {
it('ファクトリーメソッドを使用してTODOアイテムを作成すること', () => {
const todo = TodoItem.create(1, 'ファクトリーパターンを学ぶ');
expect(todo.id).toBe(1);
expect(todo.text).toBe('ファクトリーパターンを学ぶ');
expect(todo.completed).toBe(false);
});
it('ファクトリーメソッドを使用して完了済みTODOアイテムを作成すること', () => {
const todo = TodoItem.createCompleted(1, '既に学習済み');
expect(todo.id).toBe(1);
expect(todo.text).toBe('既に学習済み');
expect(todo.completed).toBe(true);
});
});
このテストは失敗します。Factory Methodが存在しないためです。
🟢2. グリーン:Factory Methodを実装する
export abstract class TodoItem {
constructor(
public id: number,
public text: string,
public completed: boolean
) {}
abstract complete(): TodoItem;
equals(other: TodoItem): boolean {
return this.id === other.id &&
this.text === other.text &&
this.completed === other.completed;
}
// Factory Methods
static create(id: number, text: string): TodoItem {
return new IncompleteTodoItem(id, text);
}
static createCompleted(id: number, text: string): TodoItem {
return new CompletedTodoItem(id, text);
}
}
class IncompleteTodoItem extends TodoItem {
constructor(id: number, text: string) {
super(id, text, false);
}
complete(): TodoItem {
return TodoItem.createCompleted(this.id, this.text);
}
}
class CompletedTodoItem extends TodoItem {
constructor(id: number, text: string) {
super(id, text, true);
}
complete(): TodoItem {
return this; // 既に完了済み
}
}
🔵3. リファクタリング:TypeScript型定義の改善
Factory Methodが通る状態になったので、より良い設計に向けてリファクタリングします。 TypeScriptの型システムを活用し、より安全で使いやすいAPIを提供します。
export interface ITodoItem {
readonly id: number;
readonly text: string;
readonly completed: boolean;
complete(): ITodoItem;
equals(other: ITodoItem): boolean;
}
export abstract class TodoItem implements ITodoItem {
constructor(
public readonly id: number,
public readonly text: string,
public readonly completed: boolean
) {}
abstract complete(): ITodoItem;
equals(other: ITodoItem): boolean {
return this.id === other.id &&
this.text === other.text &&
this.completed === other.completed;
}
// Factory Methods - 実装を隠蔽
static create(id: number, text: string): ITodoItem {
return new IncompleteTodoItem(id, text);
}
static createCompleted(id: number, text: string): ITodoItem {
return new CompletedTodoItem(id, text);
}
}
⚛️4. Next.js App Routerでの実践応用
Factory Methodを使用して、Server ComponentからTODOアイテムを生成します。
import { TodoList } from '@/components/TodoList';
import { TodoItem } from '@/lib/TodoItem';
// サンプルデータをFactory Methodで生成
function getSampleTodos() {
return [
TodoItem.create(1, 'TypeScriptでFactory Methodを学ぶ'),
TodoItem.createCompleted(2, 'TDDの基本サイクルを理解する'),
TodoItem.create(3, 'Next.js App Routerでコンポーネントを実装する'),
TodoItem.createCompleted(4, 'ファクトリーメソッドパターンを適用する')
];
}
export default function TodosPage() {
const todos = getSampleTodos();
return (
<main className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-6">TODO List</h1>
<TodoList
todos={todos}
onToggle={(id) => {
// Client Componentでハンドリング
console.log(`Toggle todo ${id}`);
}}
/>
</main>
);
}
✨設計の利点
Factory Methodパターンを導入したことで、以下の利点が得られました:
実装の隠蔽
クライアントコードは具体的なサブクラスを知る必要がない
型安全性
TypeScriptの`ITodoItem`インターフェースにより、型安全性が保たれる
拡張性
新しいTODOアイテムの種類を追加する際、Factory Methodを変更するだけで済む
テスタビリティ
Factory Methodをモックしやすく、テストが書きやすい
TODOリストの更新
- • ✅
`TodoItem`と`CompletedTodoItem`の重複
- • ✅
`TodoItem`のサブクラスを隠す
- • ✅
TypeScriptの型安全性を保つ
- • ✅ 実装を隠すファクトリーメソッド
🎯まとめ
Factory Methodパターンを使って実装の詳細を隠蔽し、より洗練された設計を実現しました。 次の章では、TDDにおける「歩幅の調整」について学び、times
実装の一般化を通じて より洗練された設計手法を身につけます。