SOLID 原則: ソフトウェア アーキテクチャの基礎
I 堅固な原則 指向性のあるコードを書くための 5 つの基本的なガイドラインがあります 高品質のオブジェクトに。ロバート C. マーティン (ボブおじさん) によって導入されたこれらの原則は、 ソフトウェア 保守可能, 拡張可能な e テスト可能。 SOLID は、単一責任、オープン/クローズ、リスコフ置換、を表す頭字語です。 インターフェイスの分離と依存関係の逆転。
🎯 5 つの確かな原則
- S - 単一責任原則 (SRP)
- O - オープン/クローズ原則 (OCP)
- L - リスコフ置換原理 (LSP)
- I - インターフェース分離原則 (ISP)
- D - 依存関係逆転原理 (DIP)
1. 単一責任原則 (SRP)
「クラスを変更する理由は 1 つだけであるべきです」
各クラスに 1 つ必要です 単一責任 明確に定義されています。これでできます コードの理解、テスト、変更が容易になります。
❌ SRP 違反:
class User {{ '{' }}
constructor(
public name: string,
public email: string
) {{ '{' }}{{ '}' }}
// Responsabilità 1: Validazione
validateEmail(): boolean {{ '{' }}
return /\S+@\S+\.\S+/.test(this.email);
{{ '}' }}
// Responsabilità 2: Persistenza
save(): void {{ '{' }}
// Salva nel database
console.log("Salvato nel DB");
{{ '}' }}
// Responsabilità 3: Notifiche
sendWelcomeEmail(): void {{ '{' }}
console.log(`Email a ${{ '{' }}this.email{{ '}' }}`);
{{ '}' }}
{{ '}' }}
✅ SRPを尊重します:
// Una responsabilità per classe
class User {{ '{' }}
constructor(
public name: string,
public email: string
) {{ '{' }}{{ '}' }}
{{ '}' }}
class UserValidator {{ '{' }}
validateEmail(email: string): boolean {{ '{' }}
return /\S+@\S+\.\S+/.test(email);
{{ '}' }}
{{ '}' }}
class UserRepository {{ '{' }}
save(user: User): void {{ '{' }}
console.log("Salvato nel DB");
{{ '}' }}
{{ '}' }}
class EmailService {{ '{' }}
sendWelcome(user: User): void {{ '{' }}
console.log(`Email a ${{ '{' }}user.email{{ '}' }}`);
{{ '}' }}
{{ '}' }}
2. オープン/クローズ原則 (OCP)
「クラスは拡張にはオープンであるべきですが、変更にはクローズドであるべきです。」
新しい機能を追加できるはずです 延長する 既存のコード、 それを変えていない。拡張を許可するには、抽象化 (インターフェイス、抽象クラス) を使用します。
class DiscountCalculator {{ '{' }}
calculate(type: string, amount: number): number {{ '{' }}
// Ogni nuovo tipo richiede modifica di questa classe!
if (type === 'student') {{ '{' }}
return amount * 0.9; // 10% sconto
{{ '}' }} else if (type === 'senior') {{ '{' }}
return amount * 0.85; // 15% sconto
{{ '}' }} else if (type === 'vip') {{ '{' }}
return amount * 0.8; // 20% sconto
{{ '}' }}
return amount;
{{ '}' }}
{{ '}' }}
// Interfaccia per estensione
interface DiscountStrategy {{ '{' }}
apply(amount: number): number;
{{ '}' }}
class StudentDiscount implements DiscountStrategy {{ '{' }}
apply(amount: number): number {{ '{' }}
return amount * 0.9;
{{ '}' }}
{{ '}' }}
class SeniorDiscount implements DiscountStrategy {{ '{' }}
apply(amount: number): number {{ '{' }}
return amount * 0.85;
{{ '}' }}
{{ '}' }}
class VIPDiscount implements DiscountStrategy {{ '{' }}
apply(amount: number): number {{ '{' }}
return amount * 0.8;
{{ '}' }}
{{ '}' }}
// Chiusa alla modifica, aperta all'estensione
class DiscountCalculatorOCP {{ '{' }}
calculate(strategy: DiscountStrategy, amount: number): number {{ '{' }}
return strategy.apply(amount);
{{ '}' }}
{{ '}' }}
// Aggiungere nuovo sconto = creare nuova classe, non modificare esistente
const calc = new DiscountCalculatorOCP();
console.log(calc.calculate(new StudentDiscount(), 100)); // 90
console.log(calc.calculate(new VIPDiscount(), 100)); // 80
3. リスコフ置換原理 (LSP)
「サブクラスのオブジェクトは、基本クラスのオブジェクトを置き換えることができなければなりません プログラムの正しい動作を変更することなく」
サブクラスは次のことを行う必要があります 伸ばす 基本クラスの動作、 期待を裏切るように変更しないでください。
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;
{{ '}' }}
{{ '}' }}
// Viola LSP: Square non può sostituire Rectangle
class Square extends Rectangle {{ '{' }}
setWidth(width: number): void {{ '{' }}
this.width = width;
this.height = width; // Effetto collaterale inaspettato!
{{ '}' }}
setHeight(height: number): void {{ '{' }}
this.width = height;
this.height = height; // Effetto collaterale inaspettato!
{{ '}' }}
{{ '}' }}
function increaseRectangleWidth(rect: Rectangle): void {{ '{' }}
rect.setWidth(rect.getArea() + 10);
// Aspettativa: solo la larghezza cambia
// Realtà con Square: anche l'altezza cambia!
{{ '}' }}
// Usa composizione invece di ereditarietà problematica
interface Shape {{ '{' }}
getArea(): number;
{{ '}' }}
class RectangleLSP implements Shape {{ '{' }}
constructor(private width: number, private height: number) {{ '{' }}{{ '}' }}
setWidth(width: number): void {{ '{' }}
this.width = width;
{{ '}' }}
setHeight(height: number): void {{ '{' }}
this.height = height;
{{ '}' }}
getArea(): number {{ '{' }}
return this.width * this.height;
{{ '}' }}
{{ '}' }}
class SquareLSP implements Shape {{ '{' }}
constructor(private side: number) {{ '{' }}{{ '}' }}
setSide(side: number): void {{ '{' }}
this.side = side;
{{ '}' }}
getArea(): number {{ '{' }}
return this.side * this.side;
{{ '}' }}
{{ '}' }}
// Funziona con qualsiasi Shape senza sorprese
function printArea(shape: Shape): void {{ '{' }}
console.log(`Area: ${{ '{' }}shape.getArea(){{ '}' }}`);
{{ '}' }}
4. インターフェース分離原則 (ISP)
「クライアントは、使用しないインターフェイスに強制的に依存すべきではありません。」
あった方が良い 小さくて特殊なインターフェース なんと大きくて汎用的なインターフェイスなのでしょう。 クラスに不要なメソッドの実装を強制する「ファット インターフェイス」は避けてください。
// Interfaccia troppo grande
interface Worker {{ '{' }}
work(): void;
eat(): void;
sleep(): void;
{{ '}' }}
class HumanWorker implements Worker {{ '{' }}
work(): void {{ '{' }} console.log("Lavoro"); {{ '}' }}
eat(): void {{ '{' }} console.log("Mangio"); {{ '}' }}
sleep(): void {{ '{' }} console.log("Dormo"); {{ '}' }}
{{ '}' }}
// Robot non mangia né dorme, ma è forzato a implementarli!
class RobotWorker implements Worker {{ '{' }}
work(): void {{ '{' }} console.log("Lavoro"); {{ '}' }}
eat(): void {{ '{' }} throw new Error("Robot non mangia"); {{ '}' }}
sleep(): void {{ '{' }} throw new Error("Robot non dorme"); {{ '}' }}
{{ '}' }}
// Interfacce segregate per responsabilità
interface Workable {{ '{' }}
work(): void;
{{ '}' }}
interface Eatable {{ '{' }}
eat(): void;
{{ '}' }}
interface Sleepable {{ '{' }}
sleep(): void;
{{ '}' }}
// Umano implementa tutte le interfacce
class HumanWorkerISP implements Workable, Eatable, Sleepable {{ '{' }}
work(): void {{ '{' }} console.log("Lavoro"); {{ '}' }}
eat(): void {{ '{' }} console.log("Mangio"); {{ '}' }}
sleep(): void {{ '{' }} console.log("Dormo"); {{ '}' }}
{{ '}' }}
// Robot implementa solo ciò che serve
class RobotWorkerISP implements Workable {{ '{' }}
work(): void {{ '{' }} console.log("Lavoro 24/7"); {{ '}' }}
{{ '}' }}
// Funzioni specifiche per interfacce specifiche
function makeWork(worker: Workable): void {{ '{' }}
worker.work();
{{ '}' }}
function feedWorker(worker: Eatable): void {{ '{' }}
worker.eat();
{{ '}' }}
const human = new HumanWorkerISP();
const robot = new RobotWorkerISP();
makeWork(human); // OK
makeWork(robot); // OK
feedWorker(human); // OK
// feedWorker(robot); // ❌ Errore compile-time: robot non è Eatable
5. 依存関係逆転原理 (DIP)
「具体的な実装ではなく、抽象化に依存します。」
高レベルのモジュールは低レベルのモジュールに依存しないでください。両方とも必要です 依存する 抽象化 (インターフェース)。抽象化は依存する必要がない しかし、詳細は抽象化に依存する必要があります。
// Dipendenza diretta da implementazione concreta
class MySQLDatabase {{ '{' }}
save(data: string): void {{ '{' }}
console.log(`Salvato in MySQL: ${{ '{' }}data{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// UserService dipende dalla classe concreta MySQLDatabase
class UserService {{ '{' }}
private db = new MySQLDatabase(); // Accoppiamento forte!
createUser(name: string): void {{ '{' }}
this.db.save(name);
{{ '}' }}
{{ '}' }}
// Cambiare database richiede modifica di UserService
// Astrazione (interfaccia)
interface Database {{ '{' }}
save(data: string): void;
{{ '}' }}
// Implementazioni concrete dipendono dall'astrazione
class MySQLDatabaseDIP implements Database {{ '{' }}
save(data: string): void {{ '{' }}
console.log(`Salvato in MySQL: ${{ '{' }}data{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class MongoDBDatabase implements Database {{ '{' }}
save(data: string): void {{ '{' }}
console.log(`Salvato in MongoDB: ${{ '{' }}data{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class PostgreSQLDatabase implements Database {{ '{' }}
save(data: string): void {{ '{' }}
console.log(`Salvato in PostgreSQL: ${{ '{' }}data{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// UserService dipende dall'astrazione, non dall'implementazione
class UserServiceDIP {{ '{' }}
// Dependency Injection: il database viene iniettato
constructor(private db: Database) {{ '{' }}{{ '}' }}
createUser(name: string): void {{ '{' }}
this.db.save(name);
{{ '}' }}
{{ '}' }}
// Facile cambiare implementazione senza modificare UserService
const mysqlService = new UserServiceDIP(new MySQLDatabaseDIP());
const mongoService = new UserServiceDIP(new MongoDBDatabase());
const pgService = new UserServiceDIP(new PostgreSQLDatabase());
mysqlService.createUser("Alice"); // MySQL
mongoService.createUser("Bob"); // MongoDB
pgService.createUser("Charlie"); // PostgreSQL
実際の SOLID: 完全な例
すべての SOLID 原則を通知システムに適用する方法を見てみましょう。
// ISP: Interfacce segregate
interface MessageSender {{ '{' }}
send(message: string, recipient: string): void;
{{ '}' }}
// DIP: Implementazioni concrete dipendono da astrazione
class EmailSender implements MessageSender {{ '{' }}
send(message: string, recipient: string): void {{ '{' }}
console.log(`📧 Email a ${{ '{' }}recipient{{ '}' }}: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class SMSSender implements MessageSender {{ '{' }}
send(message: string, recipient: string): void {{ '{' }}
console.log(`📱 SMS a ${{ '{' }}recipient{{ '}' }}: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
class PushNotificationSender implements MessageSender {{ '{' }}
send(message: string, recipient: string): void {{ '{' }}
console.log(`🔔 Push a ${{ '{' }}recipient{{ '}' }}: ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// SRP: Una sola responsabilità - gestione notifiche
class NotificationService {{ '{' }}
constructor(private sender: MessageSender) {{ '{' }}{{ '}' }} // DIP
notify(message: string, recipient: string): void {{ '{' }}
this.sender.send(message, recipient);
{{ '}' }}
{{ '}' }}
// OCP: Aperto all'estensione (nuovi logger) chiuso alla modifica
interface Logger {{ '{' }}
log(message: string): void;
{{ '}' }}
class ConsoleLogger implements Logger {{ '{' }}
log(message: string): void {{ '{' }}
console.log(`[LOG] ${{ '{' }}message{{ '}' }}`);
{{ '}' }}
{{ '}' }}
// SRP: Responsabilità separata per logging
class NotificationServiceWithLogging {{ '{' }}
constructor(
private sender: MessageSender,
private logger: Logger
) {{ '{' }}{{ '}' }}
notify(message: string, recipient: string): void {{ '{' }}
this.logger.log(`Invio notifica a ${{ '{' }}recipient{{ '}' }}`);
this.sender.send(message, recipient);
{{ '}' }}
{{ '}' }}
// Utilizzo
const emailService = new NotificationServiceWithLogging(
new EmailSender(),
new ConsoleLogger()
);
const smsService = new NotificationServiceWithLogging(
new SMSSender(),
new ConsoleLogger()
);
emailService.notify("Benvenuto!", "user@example.com");
smsService.notify("Codice: 1234", "+39123456789");
SOLID原則の利点
✅ メリット
- 保守性: コードの理解と変更が容易になる
- 拡張性: 既存の機能を壊さずに機能を追加する
- テスト容易性: 小規模で集中的なクラスはテストが容易です
- 再利用: 再利用可能なモジュール式コンポーネント
- 柔軟性: すべてを書き直さずに実装を変更する
結論
SOLID 原則は厳格なルールではなく、高品質のコードを記述するためのガイドラインです。 それらを適用するには練習と判断が必要です。場合によっては、それらにわずかに違反しても許容されます。 シンプルさ。目標はコードです 保守可能, テスト可能 e 拡張可能な、すべての原則に独断的に従わないでください。
🎯 重要なポイント
- 希望小売価格: 1 つのクラス = 1 つの責任
- OCP: 拡張にはオープン、変更にはクローズ
- LSP: サブクラスは問題なく置き換え可能
- ISP: 小さくて特殊なインターフェース
- 浸漬: 具体的なものではなく抽象的なものに依存している







