SOLID 原則
SOLID原則は、オブジェクト指向プログラミングにおける設計原則です。TypeScriptでこれらの原則を適用する方法を学びましょう。
SOLID原則とは
SOLID原則は以下の5つの原則の頭文字を取ったものです:
- Single Responsibility Principle(単一責任の原則)
- Open-Closed Principle(オープン・クローズドの原則)
- Liskov Substitution Principle(リスコフの置換原則)
- Interface Segregation Principle(インターフェース分離の原則)
- Dependency Inversion Principle(依存性逆転の原則)
単一責任の原則(SRP)
クラスは単一の責任のみを持つべきです。
Bad
❌ Bad: 複数の責任を持つクラス
// ❌ 複数の責任を持つクラス
class TodoList {
private items: string[] = [];
addItem(text: string): void {
this.items.push(text);
}
removeItem(index: number): void {
this.items.splice(index, 1);
}
toString(): string {
return this.items.join('\n');
}
// ファイル操作の責任も持っている
saveToFile(filename: string): void {
// ファイルを保存する処理
}
loadFromFile(filename: string): void {
// ファイルを読み込む処理
}
}
Good
✅ Good: 責任を分離
// ✅ 責任を分離
class TodoList {
private items: string[] = [];
addItem(text: string): void {
this.items.push(text);
}
removeItem(index: number): void {
this.items.splice(index, 1);
}
getItems(): string[] {
return [...this.items];
}
toString(): string {
return this.items.join('\n');
}
}
class FileManager {
save(filename: string, data: string): void {
// ファイルを保存する処理
}
load(filename: string): string {
// ファイルを読み込む処理
return '';
}
}
// 使用時
const todoList = new TodoList();
const fileManager = new FileManager();
todoList.addItem('Buy groceries');
fileManager.save('todos.txt', todoList.toString());
オープン・クローズドの原則(OCP)
クラスは拡張に対して開いていて、修正に対して閉じているべきです。
Bad
❌ Bad: 新しい支払い方法を追加するたびに修正が必要
// ❌ 新しい支払い方法を追加するたびに修正が必要
class PaymentProcessor {
processPayment(amount: number, method: string): void {
if (method === 'creditCard') {
console.log(`Processing credit card payment of ${amount}`);
} else if (method === 'paypal') {
console.log(`Processing PayPal payment of ${amount}`);
} else if (method === 'bitcoin') {
// 新しい支払い方法を追加するたびに修正が必要
console.log(`Processing Bitcoin payment of ${amount}`);
}
}
}
Good
✅ Good: 拡張に開いていて、修正に閉じている
// ✅ 拡張に開いていて、修正に閉じている
interface PaymentMethod {
process(amount: number): void;
}
class CreditCardPayment implements PaymentMethod {
process(amount: number): void {
console.log(`Processing credit card payment of ${amount}`);
}
}
class PayPalPayment implements PaymentMethod {
process(amount: number): void {
console.log(`Processing PayPal payment of ${amount}`);
}
}
// 新しい支払い方法を追加しても既存コードを修正する必要がない
class BitcoinPayment implements PaymentMethod {
process(amount: number): void {
console.log(`Processing Bitcoin payment of ${amount}`);
}
}
class PaymentProcessor {
processPayment(amount: number, method: PaymentMethod): void {
method.process(amount);
}
}
// 使用時
const processor = new PaymentProcessor();
processor.processPayment(100, new CreditCardPayment());
processor.processPayment(200, new BitcoinPayment());
リスコフの置換原則(LSP)
派生クラスは、基底クラスと置換可能であるべきです。
Bad
❌ Bad: 派生クラスが基底クラスの契約を破っている
// ❌ 派生クラスが基底クラスの契約を破っている
class Rectangle {
constructor(protected width: number, protected height: number) {}
setWidth(width: number): void {
this.width = width;
}
setHeight(height: number): void {
this.height = height;
}
getArea(): number {
return this.width * this.height;
}
}
class Square extends Rectangle {
constructor(size: number) {
super(size, size);
}
// 正方形は幅と高さが同じでなければならないため、契約を破る
setWidth(width: number): void {
this.width = width;
this.height = width; // 高さも変更される
}
setHeight(height: number): void {
this.width = height;
this.height = height; // 幅も変更される
}
}
// 問題が発生
function resizeRectangle(rect: Rectangle): void {
rect.setWidth(10);
rect.setHeight(5);
console.log(rect.getArea()); // Rectangle: 50, Square: 25(予期しない結果)
}