← Назад к вопросам

Расшифруй аббревиатуру SOLID

2.0 Middle🔥 162 комментариев
#HTML и CSS

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

SOLID: Принципы объектно-ориентированного проектирования

SOLID — это акроним, представляющий собой набор из пяти фундаментальных принципов объектно-ориентированного программирования и проектирования. Эти принципы были сформулированы Робертом Мартином (Дядя Боб) и призваны помочь разработчикам создавать гибкие, поддерживаемые и масштабируемые программные системы. Следование SOLID делает код менее связанным, более понятным и устойчивым к изменениям.

Каждая буква в аббревиатуре SOLID соответствует конкретному принципу:

S — Single Responsibility Principle (Принцип единственной ответственности)

"Один класс должен иметь одну и только одну причину для изменения".

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

Пример нарушения:

// Класс нарушает SRP: он отвечает и за данные пользователя, и за его сохранение.
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }

    saveToDatabase() {
        // Логика сохранения в БД
        console.log(`Сохранение пользователя ${this.name} в базу данных...`);
    }

    sendEmail(subject, message) {
        // Логика отправки email
        console.log(`Отправка письма на ${this.email}...`);
    }
}

Пример соответствия:

// Разделяем ответственности: отдельный класс для логики данных и отдельный для работы с хранилищем.
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
}

class UserRepository {
    save(user) {
        // Логика сохранения
        console.log(`Сохранение ${user.name} в БД...`);
    }
}

class EmailService {
    sendEmail(user, subject, message) {
        // Логика отправки
        console.log(`Отправка письма на ${user.email}...`);
    }
}

O — Open/Closed Principle (Принцип открытости/закрытости)

"Программные сущности (классы, модули, функции) должны быть открыты для расширения, но закрыты для модификации".

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

Пример:

// НЕСООТВЕТСТВИЕ OCP: добавление нового типа требует изменения функции.
function calculateArea(shape: { type: string; width?: number; radius?: number }): number {
    if (shape.type === 'rectangle') {
        return shape.width! * shape.width!;
    } else if (shape.type === 'circle') {
        return Math.PI * shape.radius! ** 2;
    }
    // При добавлении нового типа (triangle) придётся редактировать эту функцию.
    throw new Error('Неизвестный тип фигуры');
}

// СООТВЕТСТВИЕ OCP: используем абстракцию.
interface Shape {
    area(): number;
}

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

class Circle implements Shape {
    constructor(private radius: number) {}
    area(): number {
        return Math.PI * this.radius ** 2;
    }
}

// Новая фигура добавляется без изменения существующего кода.
class Triangle implements Shape {
    constructor(private base: number, private height: number) {}
    area(): number {
        return 0.5 * this.base * this.height;
    }
}

function calculateTotalArea(shapes: Shape[]): number {
    return shapes.reduce((sum, shape) => sum + shape.area(), 0);
}

L — Liskov Substitution Principle (Принцип подстановки Барбары Лисков)

"Объекты в программе должны быть заменяемыми на экземпляры их подтипов без изменения правильности выполнения программы".

Наследующий класс должен дополнять, а не изменять поведение родительского класса. Он должен сохранять контракт базового класса (его публичные методы и их ожидаемое поведение).

Классическое нарушение — пример с прямоугольником и квадратом:

class Rectangle {
    constructor(protected width: number, protected height: number) {}

    setWidth(width: number) { this.width = width; }
    setHeight(height: number) { this.height = height; }
    getArea(): number { return this.width * this.height; }
}

// Квадрат НЕ является корректным подтипом прямоугольника с точки зрения LSP.
class Square extends Rectangle {
    constructor(side: number) {
        super(side, side);
    }

    setWidth(width: number) {
        // Нарушение контракта: изменение ширины также меняет высоту.
        super.setWidth(width);
        super.setHeight(width);
    }

    setHeight(height: number) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

// Функция, работающая с прямоугольником, сломается при передаче квадрата.
function testRectangleArea(rect: Rectangle): void {
    rect.setWidth(5);
    rect.setHeight(4);
    console.assert(rect.getArea() === 20); // Для Square площадь будет 16, утверждение упадёт.
}

I — Interface Segregation Principle (Принцип разделения интерфейсов)

"Много специализированных интерфейсов лучше, чем один универсальный".

Клиент не должен зависеть от методов, которые он не использует. Слишком "толстые" интерфейсы необходимо разбивать на более мелкие и специфичные.

Пример:

// НЕСООТВЕТСТВИЕ ISP: "толстый" интерфейс заставляет класс реализовывать ненужные методы.
interface Worker {
    work(): void;
    eat(): void;
    sleep(): void;
}

class Robot implements Worker {
    work() { /* Робот работает */ }
    eat() { /* Робот не ест! Пустой метод */ }
    sleep() { /* Робот не спит! Пустой метод */ }
}

// СООТВЕТСТВИЕ ISP: разделяем на специфичные интерфейсы.
interface Workable {
    work(): void;
}
interface Eatable {
    eat(): void;
}
interface Sleepable {
    sleep(): void;
}

class Human implements Workable, Eatable, Sleepable {
    work() { /* Человек работает */ }
    eat() { /* Человек ест */ }
    sleep() { /* Человек спит */ }
}

class Robot implements Workable {
    work() { /* Робот работает */ }
    // Не реализует ненужные ему интерфейсы.
}

D — Dependency Inversion Principle (Принцип инверсии зависимостей)

  1. Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
  2. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Проще говоря, следует зависеть от интерфейсов или абстрактных классов, а не от конкретных реализаций. Это ключевой принцип для внедрения зависимостей (Dependency Injection) и создания тестируемого кода.

Пример:

// НЕСООТВЕТСТВИЕ DIP: модуль высокого уровня напрямую зависит от конкретного низкоуровневого модуля.
class PostgreSQLDatabase {
    save(data: string) {
        console.log(`Сохранение ${data} в PostgreSQL...`);
    }
}

class UserService {
    private db: PostgreSQLDatabase; // Жёсткая зависимость от конкретной БД.

    constructor() {
        this.db = new PostgreSQLDatabase();
    }

    createUser(userData: string) {
        this.db.save(userData);
    }
}

// СООТВЕТСТВИЕ DIP: оба уровня зависят от абстракции (интерфейса).
interface Database {
    save(data: string): void;
}

class PostgreSQLDatabase implements Database {
    save(data: string) {
        console.log(`Сохранение ${data} в PostgreSQL...`);
    }
}

class MongoDBDatabase implements Database {
    save(data: string) {
        console.log(`Сохранение ${data} в MongoDB...`);
    }
}

class UserService {
    private db: Database; // Зависимость от абстракции.

    constructor(database: Database) { // Внедрение зависимости через конструктор.
        this.db = database;
    }

    createUser(userData: string) {
        this.db.save(userData);
    }
}

// Теперь мы можем легко подменять реализацию базы данных.
const postgresService = new UserService(new PostgreSQLDatabase());
const mongoService = new UserService(new MongoDBDatabase());

Значение SOLID в современной фронтенд-разработке

Хотя SOLID зародился в контексте классического ООП, его принципы абсолютно применимы и в современном JavaScript/TypeScript и React/Vue:

  • SRP — создание небольших, переиспользуемых React-компонентов или Vue-компонентов, хуков и composable-функций, каждая из которых делает одну вещь.
  • OCP — проектирование компонентов через композицию (children, slots, render props), что позволяет расширять их поведение, не модифицируя исходный код.
  • LSP — правильное использование наследования и полиморфизма в TypeScript, а также обеспечение того, что дочерние компоненты не нарушают контракт родительских.
  • ISP — создание небольших, сфокусированных контекстов, хуков или интерфейсов в TypeScript вместо монолитных.
  • DIP — активное использование внедрения зависимостей для сервисов (например, HTTP-клиента, хранилища состояния) и абстракций над сторонними библиотеками, что критически важно для модульного тестирования.

Итог: SOLID — это не догма, а мощный инструмент для мышления о проектировании. Следование этим принципам ведёт к созданию чистой архитектуры, где код становится предсказуемым, легко тестируемым и готовым к изменениям в условиях постоянно развивающихся требований бизнеса. Нарушения SOLID часто являются первопричиной появления "спагетти-кода" и неподъёмного технического долга.

Расшифруй аббревиатуру SOLID | PrepBro