Чому високорівневі модулі не повинні залежати від низькорівневих?
Високорівневі модулі не повинні залежати від низькорівневих, тому що це порушує стійкість, гнучкість і переюзабельність системи. Цей принцип - основа Dependency Inversion Principle (DIP) з SOLID, і його мета - зробити архітектуру незалежною від деталей реалізації.
1. Що таке високо- і низькорівневі модулі
- Високорівневі модулі - містять бізнес-логіку, правила, основні сценарії роботи системи.
Приклад:
OrderService,PaymentProcessor,UserManager. - Низькорівневі модулі - займаються деталями реалізації: базою даних, файлами, мережею, API.
Приклад:
MySQLRepository,EmailSender,FileLogger.
Коли високорівневий модуль напряму використовує низькорівневий, він стає жорстко прив'язаним до конкретних технологій та інструментів, що робить систему крихкою.
2. Що відбувається при прямій залежності
class OrderService:
def __init__(self):
self.db = MySQLRepository() # низькорівнева залежність
def create_order(self, order):
self.db.save(order)Проблема:
- Якщо потрібно перейти з MySQL на PostgreSQL чи MongoDB, доведеться переписувати
OrderService. - Неможливо протестувати бізнес-логіку без реальної бази даних.
- Логіка предметної області "знає" про технічні деталі.
Тобто високорівневий код стає залежним від деталей інфраструктури, хоча повинен визначати лише що робити, а не як саме.
3. Після перевороту залежності
class Repository(Protocol):
def save(self, entity): ...
class OrderService:
def __init__(self, repository: Repository):
self.repository = repository
def create_order(self, order):
self.repository.save(order)Тепер:
OrderServiceзалежить від абстракції, а не від конкретного класу.- Низькорівневий код (
MySQLRepository,MongoRepository) реалізує цю абстракцію. - Конкретна реалізація підставляється ззовні (через IoC-контейнер чи вручну).
Таким чином залежність перевертається: деталі залежать від абстракцій, а не навпаки.
4. Навіщо це потрібно
- Ізоляція бізнес-логіки - зміни в інфраструктурі (БД, логування, API) не вимагають зміни бізнес-коду.
- Тестованість - можна підставляти моки і фейкові реалізації без реальних залежностей.
- Гнучкість і масштабованість - легко замінити технології без рефакторингу ключових модулів.
- Чиста архітектура - верхні шари (use cases, бізнес-логіка) незалежні від нижніх (база даних, UI).
5. Формулювання принципу Dependency Inversion
- Модулі високого рівня не повинні залежати від модулів низького рівня. Обидва повинні залежати від абстракцій.
- Абстракції не повинні залежати від деталей. Деталі повинні залежати від абстракцій.
Підсумок: Високорівневі модулі не повинні залежати від низькорівневих, тому що це призводить до жорсткої зв'язаності, ускладнює підтримку і тестування. Замість цього обидві сторони повинні залежати від спільних абстракцій: бізнес-логіка описує "що має бути зроблено", а деталі реалізації підлаштовуються під неї. Такий підхід робить систему гнучкою, модульною і стійкою до змін.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.