Давайте поговорим о принципах SOLID. Сначала я боялся этого, я думал, что это безумно сложно использовать в реальном кодировании, но после того, как я углубился в кодирование, я обнаружил, что моя работа особенно сложна и пара, поэтому мне нужно было что-то, чтобы решить спагетти, которые у меня были.

Введите SOLID, поскольку я изучал его и пытался понять, как реализовать его в коде реальной жизни, я начал расширять свой кругозор, а затем это стало второй натурой моего мыслительного процесса, так что поехали!

**1. Принцип единой ответственности (SPR)**
Этот принцип гласит, что каждый класс или функция должны иметь только одну ответственность. Это означает, что класс или функция не должны иметь слишком много разных задач, а вместо этого должны сосредоточиться на выполнении одной задачи и выполнении ее хорошо. Или, говоря словами дяди Боба, у него должна быть только одна причина для изменения.

Например, нам нужно закодировать функцию для пользовательской истории, в которой говорится, что клиенту необходимо создать сотрудника и сохранить его данные в базе данных, поэтому вместо класса «Сотрудник», который обрабатывает как бизнес-логику сотрудника, а также управляет подключением к базе данных, у нас должен быть класс «Сотрудник», который обрабатывает только бизнес-логику сотрудника, и отдельный класс «Подключение к базе данных», который обрабатывает подключение к базе данных. Таким образом, если нам нужно изменить способ подключения к базе данных, нам не нужно будет изменять класс «Сотрудник».

Весь код написан на машинописном языке.

```Typescript
class Employee {
частный возраст: число;
личное имя: строка;

конструктор(возраст: строка, имя: строка) {
this.age = age;
this.name = name;
}

public getAge() {
// Логика получения возраста.
}
}

class DBConnection {
public saveEmployee(employee: Employee) {
// Логика сохранения сотрудника в БД.
}
}
```

**2. Принцип открытия/закрытия (OCP)**
В нем говорится, что класс должен быть открыт для расширения, но закрыт для модификации. Это означает, что мы должны иметь возможность добавлять новые функции в класс без необходимости изменять исходный код.

Итак, подумайте об этом сценарии: есть новая функция, которую необходимо развернуть, и это следует сделать в коде, который уже разработан, протестирован и работает в производственной среде. Последнее, что вам нужно, — это изменить этот код на беспорядок, поэтому вы должны заранее подумать об этом сценарии и кодировать таким образом, чтобы он был масштабируемым и простым в обслуживании.

Например, если у нас есть класс «Транспортное средство» с методом «перемещение», если мы хотим добавить новый тип транспортного средства, например лодку, вместо изменения класса «Транспортное средство» и добавления новой функции «парус», мы должны создать новый класс «Лодка», который расширяет класс «Транспортное средство» и добавляет новую функциональность.

```Typescript
class Vehicle {
public move() {
// Логика для перемещения
}
}

class Boat extends Vehicle {
public navigation() {
// Логика навигации
}
}
```

**3. Принцип замещения Лискова (LSP)**
Это проще, чем кажется. Он гласит, что экземпляр производного класса должен иметь возможность использоваться везде, где ожидается экземпляр базового класса, без изменения правильности программы. .

Другими словами, если у нас есть базовый класс и производный класс, который расширяет базовый класс, любой объект производного класса должен иметь возможность использоваться вместо объекта базового класса, не вызывая ошибок или неожиданного поведения.

*Пожалуйста, не используйте наследование, используйте абстрактный класс, ваше будущее будет вам благодарно*

Например, если у нас есть базовый класс «Shape», который имеет метод «calculateArea», и производный класс «Circle», который расширяет «Shape», то вместо объекта можно использовать объект «Circle». «Shape» в любом месте кода, где ожидается «Shape».

```Typescript
abstract class Figure {
public abstract getArea(): number;
}

class Circle extends Figure {
частный радиус: число;

конструктор(радиус: число) {
супер();
это.радиус = радиус;
}

public getArea() {
return Math.PI * Math.pow(this.radius, 2);
}

public getPerimeter() {
return 2 * Math.PI * this.radius;
}
}

// Использовать круг там, где ожидается фигура
function printArea(figura: Figure) {
console.log(`Область фигуры: ${figure.getArea()}`);
}

const круг = новый круг(5);
printArea(круг);
/* Это не приведет к ошибкам, так как объект Circle
*может использоваться вместо объекта Figure и
*программа продолжит работу
*
* Попробуйте создать другой класс для любой фигуры и
*просто измените Circle(5) на созданный класс
*/
```

**4. Принцип разделения интерфейса (ISP)**
Вы когда-нибудь видели класс, в котором реализовано множество неиспользуемых методов? Интернет-провайдер утверждает, что класс не следует заставлять реализовывать методы, которые ему не нужны. Это означает, что у нас должны быть меньшие и более специфические интерфейсы вместо одного большого и сложного интерфейса.

Другими словами, давайте сначала подумаем об интерфейсе (да, сначала о дизайне, а потом о кодировании).

Допустим, мы моделируем работников ИТ-компании, если у нас есть интерфейс «Рабочий», в котором есть методы «работа», «отдых» и «посещение совещаний», а классу «Разработчик» нужны только «работа» и «отдых», то мы не должны заставлять класс «Разработчик» реализовывать метод «посещать собрания».

Итак, давайте подумаем о том, что человек может делать в компании, и разделим это на несколько интерфейсов:

```Typescript
interface Worker {
rest(): void;
work(): void;
}

интерфейс AttendMeeting {
AttendMeeting(): void;
}

class Dev реализует Worker {
public work() {
// Логика для работы в качестве разработчика
}

public rest() {
// Логика для отдыха в качестве разработчика
}
}

class Coordinator реализует Worker, AttendMeeting {
public work() {
// Логика работы в качестве координатора
}

public rest() {
// Логика отдыха в качестве координатора
}

public AttenMeeting() {
// Логика посещения собраний в качестве координатора
}
}
```

**5. Принцип инверсии зависимостей (DIP)**
Вы когда-нибудь возвращались к коду, который написали некоторое время назад? Ну, DIP стремится сделать код более гибким и простым в обслуживании в будущем.

Если вы проводите юнит-тестирование (а вам следует это сделать), то этот принцип — чистое золото.

Основная идея состоит в том, что высокоуровневые модули не должны зависеть от низкоуровневых модулей, но оба должны зависеть от абстракций. Это означает, что мы должны зависеть от интерфейсов, а не от конкретных реализаций.

Допустим, вам поручили написать всегда забавную функцию, которая позволяет пользователям входить в систему (кого я шучу). Если вы напрямую зависите от конкретной реализации службы входа в систему, то если в будущем вы захотите изменить способ работы входа, вам придется изменить весь свой код (ура!).

Вместо этого, если вы зависите от абстракции, такой как интерфейс, определяющий функциональные возможности службы входа в систему, вы можете легко изменить реализацию без необходимости изменения остального кода.

```Typescript
interface AuthService {
login(email: string, password: string): void;
}

класс FirebaseAuthService реализует AuthService {
конструктор() {}

public login(email: string, password: string) {
// Логика входа с использованием Firebase Authentication
}
}

class User {
конструктор (частный authService: AuthService) {}

общедоступный логин (адрес электронной почты: строка, пароль: строка) {
this.authService.login (адрес электронной почты, пароль);
}
}

const authService = new FirebaseAuthService();
const user = new User(authService);
```
В предыдущем коде мы создали класс FirebaseAuthService, реализующий интерфейс AuthService, а затем внедрили его пользователю через конструктор, что означает, что теперь мы зависим от абстракции, а не от конкретной реализации, и мы можем изменить это для всего, что реализует интерфейс AuthService (помните LSP).

Я надеюсь, что это объяснение поможет вам лучше понять SOLID и научиться их реализовывать, а также увидеть, что принципы следует рассматривать как единое целое (думайте в SOLID как об одном, а не как о отдельных принципах).