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

Что такое SOLID?

2.3 Middle🔥 151 комментариев
#Soft skills и карьера

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

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

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

SOLID: Пять принципов объектно-ориентированного программирования для разработки устойчивых систем

SOLID — это набор из пяти фундаментальных принципов объектно-ориентированного программирования и дизайна, которые помогают создавать гибкие, масштабируемые и легко поддерживаемые программные системы. Как QA Engineer, понимание этих принципов критически важно для эффективного анализа архитектуры приложения, разработки тестов, особенно модульных, и выявления потенциальных проблем в коде на ранних этапах.

Краткое описание каждого принципа SOLID

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

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

// Плохой пример: Класс выполняет две задачи — управление данными пользователя и отправку email.
class UserManager {
    private User user;

    public void saveUserToDatabase() {
        // ... код для сохранения в БД
    }

    public void sendWelcomeEmail() {
        // ... код для отправки email
    }
}

// Хороший пример: Разделение ответственностей на два класса.
class UserRepository {
    public void save(User user) {
        // ... только сохранение в БД
    }
}

class EmailService {
    public void sendWelcomeEmail(User user) {
        // ... только отправка email
    }
}

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

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

// Плохой пример: Для добавления нового типа оплаты нужно изменять существующий метод.
class PaymentProcessor {
    processPayment(type: string, amount: number) {
        if (type === "creditCard") {
            // ... логика для карты
        } else if (type === "paypal") {
            // ... логика для PayPal
        }
        // Добавление нового типа (например, "crypto") требует изменения этого метода!
    }
}

// Хороший пример: Использование абстракции. Новые платежные методы добавляются новыми классами.
interface PaymentStrategy {
    process(amount: number): void;
}

class CreditCardStrategy implements PaymentStrategy {
    process(amount: number) { /* ... */ }
}

class PayPalStrategy implements PaymentStrategy {
    process(amount: number) { /* ... */ }
}

class PaymentProcessor {
    private strategy: PaymentStrategy;

    setStrategy(strategy: PaymentStrategy) {
        this.strategy = strategy;
    }

    processPayment(amount: number) {
        this.strategy.process(amount); // Код PaymentProcessor не меняется для новых стратегий!
    }
}

// Новый метод добавляется без изменения PaymentProcessor:
class CryptoStrategy implements PaymentStrategy {
    process(amount: number) { /* ... */ }
}

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

Объекты в программе должны быть заменяемыми экземплярами их подтипов без изменения корректности программы. Дочерние классы не должны нарушать контракт (ожидаемое поведение) родительского класса. Это гарантирует, что наследование используется корректно.

# Плохой пример: Класс Square нарушает поведение класса Rectangle.
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def set_width(self, width):
        self.width = width

    def set_height(self, height):
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)

    def set_width(self, width):
        self.width = width
        self.height = width  # Нарушение: изменение width меняет height, что неожиданно для "Rectangle"

# Хороший пример: Использование общей абстракции без нарушающего наследования.
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

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

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

// Плохой пример: Один огромный интерфейс для всех устройств.
interface IMultiFunctionDevice {
    void Print(Document d);
    void Scan(Document d);
    void Fax(Document d);
}

class OldPrinter : IMultiFunctionDevice {
    public void Print(Document d) { /* ... */ }
    public void Scan(Document d) { throw new NotImplementedException(); } // Не используется!
    public void Fax(Document d) { throw new NotImplementedException(); } // Не используется!
}

// Хороший пример: Разделение на специфичные интерфейсы.
interface IPrinter {
    void Print(Document d);
}

interface IScanner {
    void Scan(Document d);
}

interface IFax {
    void Fax(Document d);
}

class OldPrinter : IPrinter { // Реализует только необходимый функционал.
    public void Print(Document d) { /* ... */ }
}

class ModernAllInOne : IPrinter, IScanner, IFax {
    public void Print(Document d) { /* ... */ }
    public void Scan(Document d) { /* ... */ }
    public void Fax(Document d) { /* ... */ }
}

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

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

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

// Плохой пример: Класс верхнего уровня напрямую зависит от конкретного класса нижнего уровня.
class OrderService { // Модуль верхнего уровня (бизнес-логика)
    private MySQLDatabase database; // Конкретная зависимость от модуля нижнего уровня (доступ к данным)

    public void saveOrder(Order order) {
        database.save(order);
    }
}

// Хороший пример: Оба уровня зависят от абстракции (интерфейса).
interface DatabaseRepository { // Абстракция
    void save(Order order);
}

class MySQLDatabase implements DatabaseRepository { // Детали (конкретная реализация)
    public void save(Order order) { /* ... */ }
}

class OrderService { // Модуль верхнего уровня
    private DatabaseRepository repository; // Зависимость от абстракции!

    public OrderService(DatabaseRepository repo) { // Инъекция зависимости (DI)
        this.repository = repo;
    }

    public void saveOrder(Order order) {
        repository.save(order);
    }
}

Почему SOLID важен для QA Engineer?

  • Упрощает модульное тестирование (Unit Testing):
    *   Классы с **единственной ответственностью (SRP)** имеют четко определенную функциональность, что позволяет легко писать небольшие, фокусированные тесты.
    *   **Инверсия зависимостей (DIP)** и использование интерфейсов позволяют легко заменять реальные зависимости (например, базу данных или внешний API) на **mock-объекты или stub-ы** в тестах, обеспечивая их независимость и скорость.
  • Улучшает понимание системы: Знание SOLID помогает QA анализировать архитектуру и дизайн системы, выявлять потенциальные "точки напряжения" (например, классы, нарушающие SRP, которые могут быть подвержены частым изменениям и ошибкам).
  • Помогает в рецензировании кода (Code Review): QA может участвовать в проверке кода на предмет соблюдения этих принципов, что напрямую влияет на надежность и тестируемость продукта.
  • Поддерживает тестирование на уровне интеграции и системы: Системы, построенные по принципам SOLID, обычно имеют более четкие границы модулей и слабую связанность. Это уменьшает вероятность того, что изменение в одной части системы непредсказуемо сломает другую, что критично для интеграционного и регрессионного тестирования.

Таким образом, SOLID — это не просто теория для разработчиков. Это практический инструмент для создания качественного, тестируемого и устойчивого к изменениям кода. QA Engineer, понимающий эти принципы, становится более ценным членом команды, способным вносить свой вклад в улучшение качества продукта на самом фундаментальном уровне — уровне его архитектуры и дизайна.

Что такое SOLID? | PrepBro