Проект растет, классы обрастают обязанностями, и любое изменение в одном месте ломает код в другом. Знакомая ситуация для разработчиков, которые не задумывались об архитектуре на старте.
SOLID принципы программирования помогают избежать этого хаоса. Разберем, что стоит за каждой буквой аббревиатуры, и покажем, как применять эти принципы на примере интернет-магазина.
Что такое SOLID в программировании
SOLID — это пять принципов проектирования классов в объектно-ориентированном программировании. Они задают правила, по которым код остается понятным, гибким и устойчивым к изменениям.
Что такое SOLID принципы простыми словами? Это набор рекомендаций, которые помогают разработчику организовать код так, чтобы каждый класс решал одну задачу, новые функции добавлялись без переписывания старых, а компоненты можно было заменять без поломок.
Это как бытовая техника на кухне: чайник кипятит воду, тостер жарит хлеб, кофеварка варит кофе. Если сломался тостер — вы меняете тостер, а не перестраиваете всю кухню.
Принципы сформулировал Роберт Мартин — американский инженер, автор книги «Чистый код». В начале 2000-х он описал подходы к проектированию в статье «Design Principles and Design Patterns». В 2004 году консультант Майкл Физерс объединил их под аббревиатурой SOLID.

Принципы SOLID в программировании
SOLID в объектно-ориентированном программировании работает как правила дорожного движения для кода. Можно ехать и без них — но чем больше участников и сложнее маршрут, тем выше риск аварии. Принципы проектирования SOLID не гарантируют идеальный код, но снижают вероятность хаоса при росте проекта.
Для чего нужны принципы SOLID:
упрощение поддержки — изменение в одном месте не ломает другие части системы;
ускорение разработки — новый функционал добавляется без переписывания старого кода;
облегчение тестирования — каждый компонент можно проверить отдельно;
улучшение читаемости — другой разработчик быстрее разберется в проекте.
Как расшифровывается SOLID
Каждая буква — отдельный принцип. Вот что входит в принципы SOLID:
Буква | Название | Суть |
S | Single Responsibility Principle (SRP) | Один класс — одна задача |
O | Open/Closed Principle (OCP) | Открыт для расширения, закрыт для изменения |
L | Liskov Substitution Principle (LSP) | Дочерний класс можно подставить вместо родительского — код не сломается |
I | Interface Segregation Principle (ISP) | Каждый интерфейс — под свою задачу |
D | Dependency Inversion Principle (DIP) | Компоненты подключаются через общий разъем, а не напрямую |
Что такое SRP, OCP, LSP, ISP, DIP? Это сокращения пяти принципов SOLID, каждый из которых решает конкретную проблему архитектуры кода. Рассмотрим их на примере интернет-магазина — с кодом на Python и Java.
Принцип единственной ответственности (SRP)
Принцип единственной ответственности SOLID звучит так: каждый класс должен отвечать только за одну задачу. Если класс выполняет несколько несвязанных функций, изменение одной из них может сломать остальные.
Представьте сотрудника в магазине, который одновременно принимает заказы, готовит еду и доставляет посылки. Если изменится маршрут доставки, это не должно повлиять на рецепты.
В коде — та же логика: разделяйте обязанности.
Допустим, класс Order в интернет-магазине рассчитывает сумму, сохраняет заказ в базу и отправляет уведомление клиенту:
Python — с нарушением SRP:
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
return sum(item.price for item in self.items)
def save_to_db(self):
# Сохранение заказа в базу данных
print("Заказ сохранен")
def send_notification(self):
# Отправка уведомления клиенту
print("Уведомление отправлено")
Три задачи в одном классе. Если изменится способ уведомления, придется менять класс Order — хотя к расчету суммы это не имеет отношения.
Python — с учетом SRP:
class Order:
def __init__(self, items):
self.items = items
def calculate_total(self):
return sum(item.price for item in self.items)
class OrderRepository:
def save(self, order):
# Сохранение заказа в базу данных
print("Заказ сохранен")
class NotificationService:
def send(self, message):
# Отправка уведомления клиенту
print("Уведомление отправлено")
Теперь Order отвечает за данные заказа, OrderRepository — за хранение, NotificationService — за уведомления. Изменения в одном классе не затрагивают другие.
Java — с учетом SRP:
class Order {
List<Item> items;
double calculateTotal() {
return items.stream()
.mapToDouble(item -> item.price)
.sum();
}
}
class OrderRepository {
void save(Order order) {
// Сохранение заказа в базу данных
}
}
class NotificationService {
void send(String message) {
// Отправка уведомления клиенту
}
}
Принципы SOLID в Java работают так же: один класс — одна причина для изменения.
Принцип открытости/закрытости (OCP)
Принцип открытости/закрытости SOLID утверждает: код должен быть открыт для расширения, но закрыт для модификации. Новый функционал добавляется через новые классы, а не через правку существующих.
Это похоже на розетку с адаптером. Чтобы подключить новое устройство, вы не перепаиваете проводку в стене — вы вставляете подходящий переходник.
В интернет-магазине нужно поддерживать разные способы оплаты. Без OCP каждый новый способ требует изменения одного и того же класса:
Python — с нарушением OCP:
class PaymentProcessor:
def process(self, payment_type, amount):
if payment_type == "card":
print(f"Оплата картой: {amount} ₽")
elif payment_type == "cash":
print(f"Оплата наличными: {amount} ₽")
# Каждый новый способ — новый elif
Python — с учетом OCP:
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self, amount):
pass
class CardPayment(PaymentMethod):
def pay(self, amount):
print(f"Оплата картой: {amount} ₽")
class CashPayment(PaymentMethod):
def pay(self, amount):
print(f"Оплата наличными: {amount} ₽")
class MobilePayment(PaymentMethod):
def pay(self, amount):
print(f"Оплата через приложение: {amount} ₽")
Добавление мобильной оплаты не потребовало менять существующие классы. Создали новый MobilePayment — и все работает.
Java — с учетом OCP:
interface PaymentMethod {
void pay(double amount);
}
class CardPayment implements PaymentMethod {
public void pay(double amount) {
System.out.println("Оплата картой: " + amount + " ₽");
}
}
class CashPayment implements PaymentMethod {
public void pay(double amount) {
System.out.println("Оплата наличными: " + amount + " ₽");
}
}
Как применять SOLID в программировании на практике? Создавайте интерфейсы и наследуйте от них — вместо того, чтобы дописывать условия в готовый код.
Принцип подстановки Барбары Лисков (LSP)
Подкласс должен заменять родительский класс без нарушения работы программы. Если функция принимает объект базового класса, любой его наследник должен работать так же корректно.
Допустим, в интернет-магазине есть доставка. Обычная — 50 ₽ за килограмм, экспресс — 150 ₽. Корзина вызывает метод calculate(weight) и прибавляет результат к сумме заказа. Потом разработчик добавляет самовывоз — и вместо стоимости метод выбрасывает ошибку. Корзина, которая привыкла получать число, ломается.
Python — с нарушением LSP:
class Shipping:
def calculate(self, weight):
return weight * 50 # 50 ₽ за кг
class ExpressShipping(Shipping):
def calculate(self, weight):
return weight * 150 # 150 ₽ за кг
class PickupShipping(Shipping):
def calculate(self, weight):
raise Exception("Самовывоз — доставка не нужна")
Корзина перебирает заказы и вызывает calculate() у каждого способа доставки. На PickupShipping программа падает — этот наследник нельзя подставить вместо базового Shipping.
Python — с учетом LSP:
class Shipping:
def calculate(self, weight):
return weight * 50 # 50 ₽ за кг
class ExpressShipping(Shipping):
def calculate(self, weight):
return weight * 150 # 150 ₽ за кг
class PickupShipping(Shipping):
def calculate(self, weight):
return 0 # Самовывоз бесплатный
Все наследники возвращают число. PickupShipping возвращает 0 — контракт сохранен, корзина работает без ошибок.
Java — с учетом LSP:
abstract class Shipping {
abstract double calculate(double weight);
}
class StandardShipping extends Shipping {
double calculate(double weight) {
return weight * 50; // 50 ₽ за кг
}
}
class ExpressShipping extends Shipping {
double calculate(double weight) {
return weight * 150; // 150 ₽ за кг
}
}
class PickupShipping extends Shipping {
double calculate(double weight) {
return 0; // Самовывоз бесплатный
}
}
Принцип разделения интерфейсов (ISP)
Клиенты не должны зависеть от методов, которые они не используют. Вместо одного большого интерфейса лучше создать несколько — каждый под свою задачу.
Например, в магазине есть кассир и курьер. Оба — сотрудники. Но кассиру не нужен метод «доставить заказ», а курьеру — «принять оплату». Заставлять каждого реализовывать чужие обязанности — лишняя работа.
Python — с нарушением ISP:
python
class Employee:
def accept_payment(self):
pass
def deliver_order(self):
pass
class Cashier(Employee):
def accept_payment(self):
print("Оплата принята")
def deliver_order(self):
raise NotImplementedError # Кассир не доставляет
Здесь Cashier вынужден реализовывать метод deliver_order(), который ему не нужен. Если таких методов станет больше — класс обрастет заглушками.
Python — с учетом ISP:
python
class Payable:
def accept_payment(self):
pass
class Deliverable:
def deliver_order(self):
pass
class Cashier(Payable):
def accept_payment(self):
print("Оплата принята")
class Courier(Deliverable):
def deliver_order(self):
print("Заказ доставлен")
Каждый класс реализует только нужные методы. Кассир принимает оплату, курьер доставляет.
Java — с учетом ISP:
java
interface Payable {
void acceptPayment();
}
interface Deliverable {
void deliverOrder();
}
class Cashier implements Payable {
public void acceptPayment() {
System.out.println("Оплата принята");
}
}
class Courier implements Deliverable {
public void deliverOrder() {
System.out.println("Заказ доставлен");
}
}
Принцип инверсии зависимостей (DIP)
SOLID принцип инверсии зависимостей утверждает: модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракций.
Представьте USB-порт. Вы подключаете к ноутбуку любое устройство через стандартный разъем. Ноутбук не знает, флешка это или принтер. Он работает с интерфейсом, а не с конкретным устройством.
В интернет-магазине сервис уведомлений не должен зависеть от конкретного способа отправки:
Python — с нарушением DIP:
class EmailSender:
def send(self, message):
print(f"Email: {message}")
class OrderService:
def __init__(self):
# Жесткая зависимость от EmailSender
self.notifier = EmailSender()
def place_order(self, order):
self.notifier.send("Заказ оформлен")
Если магазин перейдет на SMS или push-уведомления, придется менять OrderService.
Python — с учетом DIP:
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message):
pass
class EmailNotifier(Notifier):
def send(self, message):
print(f"Email: {message}")
class SmsNotifier(Notifier):
def send(self, message):
print(f"SMS: {message}")
class OrderService:
def __init__(self, notifier: Notifier):
self.notifier = notifier
def place_order(self, order):
self.notifier.send("Заказ оформлен")
# Используем любую реализацию
service = OrderService(SmsNotifier())
service.place_order("Заказ #42")
Java — с учетом DIP:
interface Notifier {
void send(String message);
}
class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Email: " + message);
}
}
class SmsNotifier implements Notifier {
public void send(String message) {
System.out.println("SMS: " + message);
}
}
class OrderService {
private Notifier notifier;
OrderService(Notifier notifier) {
this.notifier = notifier;
}
void placeOrder() {
notifier.send("Заказ оформлен");
}
}
OrderService зависит от интерфейса Notifier, а не от конкретного EmailNotifier. Замена способа уведомления не требует изменения бизнес-логики.
Примеры применения SOLID в коде
Как работают принципы SOLID в разработке, если применять их вместе? Перед тем как создать или изменить класс, задайте себе пять вопросов:
Этот класс делает больше одной вещи? Разделите на несколько (SRP).
Чтобы добавить новый способ оплаты или доставки, придется переписывать старый код? Создайте интерфейс и наследуйте от него (OCP).
Наследник ведет себя не так, как родитель? Исправьте — или откажитесь от наследования (LSP).
Класс вынужден реализовывать методы, которые ему не нужны? Разбейте интерфейс на части (ISP).
Класс жестко привязан к конкретному классу внутри себя? Передавайте зависимость снаружи (DIP).

Код до и после применения SOLID
Типичные ошибки при использовании SOLID
Как использовать SOLID в проекте правильно? Для начала — избежать распространенных ошибок:
Дробление ради дробления. Разделить класс из пяти строк на три класса по две строки — не SRP, а переусложнение. Принцип работает для классов, у которых действительно несколько причин для изменения.
Абстракции без необходимости. Создавать интерфейс для класса, у которого будет только одна реализация — не OCP, а лишний слой кода. Абстракции нужны там, где ожидается расширение.
Путаница LSP с наследованием. LSP не запрещает переопределять методы. Он требует, чтобы переопределенные методы сохраняли контракт родителя: принимали те же типы, возвращали совместимые результаты.
Слишком узкие интерфейсы. ISP не требует создавать интерфейс на каждый метод. Если два метода всегда работают вместе — connect() и disconnect() — они должны быть в одном интерфейсе.
Внедрение зависимостей везде подряд. DIP полезен для компонентов, которые могут меняться. Инжектировать примитивные утилиты через конструктор — избыточно.
Ограничения и критика SOLID
SOLID — набор ориентиров, которые помогают делать код управляемым. Почему используют SOLID принципы в одних проектах и обходятся без них в других? Все зависит от масштаба и целей.
Когда SOLID не нужен:
Одноразовые скрипты для автоматизации рутинных задач.
Прототипы и MVP, где важна скорость запуска.
Небольшие проекты с одним разработчиком и минимумом логики.
Также есть альтернативные подходы к проектированию:
KISS (Keep It Simple, Stupid) — пишите проще, не усложняйте без необходимости.
YAGNI (You Aren't Gonna Need It) — не добавляйте функциональность заранее.
DRY (Don't Repeat Yourself) — избегайте дублирования кода.
Как улучшить архитектуру кода с SOLID? Используйте принципы там, где проект растет и усложняется. Если класс из 20 строк справляется с задачей — не стоит превращать его в систему из пяти файлов.
SOLID что дает в долгосрочной перспективе: более предсказуемый код, легкое добавление новых функций и быстрый онбординг новых участников команды.
Заключение
Пять принципов SOLID — SRP, OCP, LSP, ISP, DIP — помогают строить архитектуру кода, которая не рассыпается при росте проекта. Они не усложняют разработку, а задают рамки, внутри которых код остается управляемым.
Если вы хотите глубже разобраться в принципах разработки SOLID и научиться применять их в реальных задачах, обратите внимание на курсы ProductStar. Обучение построено вокруг практики: от написания первых классов до проектирования архитектуры приложений.
Подробнее о программе и вариантах обучения вы можете узнать на страницах курсов ProductStar.
FAQ
Что означает SOLID в программировании?
SOLID — это аббревиатура из пяти принципов объектно-ориентированного проектирования. Они помогают писать код, который легко поддерживать, расширять и тестировать.
SOLID принципы ООП — что это?
Это набор из пяти правил для проектирования классов: единственная ответственность, открытость/закрытость, подстановка Лисков, разделение интерфейсов и инверсия зависимостей. Каждое правило решает конкретную проблему архитектуры.
Для чего нужен SOLID?
Принципы SOLID нужны, чтобы изменение одной части системы не ломало остальные. Они упрощают добавление новых функций, снижают количество багов и ускоряют командную разработку.
Как применять SOLID в программировании новичку?
Начните с SRP — разделяйте классы по задачам. Затем освойте OCP — добавляйте новый функционал через наследование, а не через правку существующего кода. Остальные принципы подключайте по мере роста проекта.













