Впровадження принципів SOLID у TypeScript

pic

Принципи SOLID — це набір принципів проектування, які допомагають розробникам створювати чисте, зручне для підтримки та масштабоване програмне забезпечення. Ці принципи особливо важливі в об'єктно-орієнтованому програмуванні та можуть бути ефективно застосовані в TypeScript.

У цій статті ми розглянемо кожен із принципів SOLID і продемонструємо, як їх реалізувати в TypeScript з практичними прикладами.

1. Принцип єдиної відповідальності (SRP)

Клас повинен мати лише одну причину для зміни, тобто він повинен виконувати лише одну відповідальність.

Погана реалізація

class User {  
 constructor(private name: string, private email: string) {}  

 saveToDatabase(): void {  
 console.log(`Saving user ${this.name} to database...`);  
 }  

 sendEmail(subject: string, body: string): void {  
 console.log(`Sending email to ${this.email}: ${subject}`);  
 }  
}

Проблема: Клас User обробляє як керування даними користувача, так і відправку електронної пошти, що порушує принцип SRP.

Гарна реалізація

Розділімо відповідальності на окремі класи (UserRepository для операцій з базою даних і EmailService для відправки електронної пошти).

class User {  
 constructor(private name: string, private email: string) {}  
}  

class UserRepository {  
 saveToDatabase(user: User): void {  
 console.log(`Saving user ${user.name} to database...`);  
 }  
}  

class EmailService {  
 sendEmail(user: User, subject: string, body: string): void {  
 console.log(`Sending email to ${user.email}: ${subject}`);  
 }  
}

2. Принцип відкритості/закритості (OCP)

Програмні сутності (класи, модулі, функції) повинні бути відкритими для розширення, але закритими для модифікації.

Погана реалізація

class Discount {  
 giveDiscount(customerType: string): number {  
 if (customerType === "regular") {  
 return 10;  
 } else if (customerType === "premium") {  
 return 20;  
 }  
 return 0;  
 }  
}

Проблема: Додавання нового типу клієнта вимагає зміни класу Discount.

Гарна реалізація

Використовуйте інтерфейси та наслідування для розширення функціональності без змін в існуючому коді.

interface Customer {  
 getDiscount(): number;  
}  

class RegularCustomer implements Customer {  
 getDiscount(): number {  
 return 10;  
 }  
}  

class PremiumCustomer implements Customer {  
 getDiscount(): number {  
 return 20;  
 }  
}  

class Discount {  
 giveDiscount(customer: Customer): number {  
 return customer.getDiscount();  
 }  
}

3. Принцип підстановки Ліскова (LSP)

Об'єкти суперкласу повинні бути замінюваними об'єктами підкласу без зміни коректності програми.

Погана реалізація

class Rectangle {  
 constructor(public width: number, public height: number) {}  
 setWidth(width: number): void {  
 this.width = width;  
 }  
 setHeight(height: number): void {  
 this.height = height;  
 }  
 area(): number {  
 return this.width * this.height;  
 }  
}  

class Square extends Rectangle {  
 setWidth(width: number): void {  
 this.width = width;  
 this.height = width; // Порушує LSP  
 }  
 setHeight(height: number): void {  
 this.height = height;  
 this.width = height; // Порушує LSP  
 }  
}

Проблема: Клас Square змінює поведінку класу Rectangle, що порушує принцип LSP.

Гарна реалізація

Використовуйте спільний інтерфейс (Shape), щоб забезпечити замінність.

interface Shape {  
 area(): number;  
}  

class Rectangle implements Shape {  
 constructor(public width: number, public height: number) {}  
 area(): number {  
 return this.width * this.height;  
 }  
}  

class Square implements Shape {  
 constructor(public side: number) {}  
 area(): number {  
 return this.side * this.side;  
 }  
}

4.

Принцип сегрегації інтерфейсів (ISP)

Клієнти не повинні бути змушені залежати від інтерфейсів, які вони не використовують.

Погана реалізація

interface Worker {  
 work(): void;  
 eat(): void;  
}  

class Engineer implements Worker {  
 work(): void {  
 console.log("Engineering work...");  
 }  
 eat(): void {  
 console.log("Eating...");  
 }  
}  

class Robot implements Worker {  
 work(): void {  
 console.log("Building...");  
 }  
 eat(): void {  
 throw new Error("Robots don't eat!");  
 }  
}

Проблема: Robot змушений реалізовувати метод eat, хоча він йому не потрібен.

Гарна реалізація

Розділімо інтерфейс на менші, більш специфічні інтерфейси.

interface Workable {  
 work(): void;  
}  

interface Eatable {  
 eat(): void;  
}  

class Engineer implements Workable, Eatable {  
 work(): void {  
 console.log("Engineering work...");  
 }  
 eat(): void {  
 console.log("Eating...");  
 }  
}  

class Robot implements Workable {  
 work(): void {  
 console.log("Building...");  
 }  
}

5. Принцип інверсії залежностей (DIP)

Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій.

Погана реалізація

class MySQLDatabase {  
 save(data: string): void {  
 console.log(`Saving ${data} to MySQL database...`);  
 }  
}  

class App {  
 private database = new MySQLDatabase();  
 saveData(data: string): void {  
 this.database.save(data);  
 }  
}

Проблема: Клас App тісно пов'язаний з класом MySQLDatabase.

Гарна реалізація

Використовуйте ін'єкцію залежностей і залежіть від абстракцій (інтерфейсів), а не від конкретних реалізацій.

interface Database {  
 save(data: string): void;  
}  

class MySQLDatabase implements Database {  
 save(data: string): void {  
 console.log(`Saving ${data} to MySQL database...`);  
 }  
}  

class MongoDBDatabase implements Database {  
 save(data: string): void {  
 console.log(`Saving ${data} to MongoDB database...`);  
 }  
}  

class App {  
 constructor(private database: Database) {}  
 saveData(data: string): void {  
 this.database.save(data);  
 }  
}  

const mySQLApp = new App(new MySQLDatabase());  
mySQLApp.saveData("User Data");  

const mongoDBApp = new App(new MongoDBDatabase());  
mongoDBApp.saveData("User Data");

Висновок

Дотримуючись принципів SOLID, ви можете створювати TypeScript додатки, які є:

  1. Модульними (Принцип єдиної відповідальності).
  2. Розширюваними (Принцип відкритості/закритості).
  3. Надійними (Принцип підстановки Ліскова).
  4. Гнучкими (Принцип сегрегації інтерфейсів).
  5. Розділеними (Принцип інверсії залежностей).

Ці принципи допомагають писати чистий, зручний для підтримки та масштабований код, що робить ваші додатки легшими для розуміння, розширення та налагодження.

Почніть застосовувати принципи SOLID у своїх TypeScript проектах вже сьогодні!

Перекладено з: Implementing SOLID Principles in TypeScript

Leave a Reply

Your email address will not be published. Required fields are marked *