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

Зачем нужна инверсия зависимостей?

1.7 Middle🔥 131 комментариев
#Архитектура и паттерны

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Инверсия зависимостей (Dependency Inversion Principle)

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

Проблема без инверсии зависимостей

# ❌ Плохо: высокоуровневый модуль зависит от низкоуровневого

class MySQLDatabase:
    def connect(self):
        print('Connecting to MySQL')
    
    def save_user(self, user):
        print(f'Saving {user} to MySQL')

class UserService:
    def __init__(self):
        self.db = MySQLDatabase()  # Жестко привязан к MySQL!
    
    def create_user(self, name):
        self.db.save_user(name)

# Проблемы:
# 1. Если нужна PostgreSQL — переписывать UserService
# 2. Тестировать UserService нельзя без настоящей БД
# 3. UserService знает о внутренностях БД
# 4. Не хочется при смене БД менять бизнес-логику

Решение: инверсия зависимостей

# ✅ Хорошо: модули зависят от абстракции

from abc import ABC, abstractmethod

# Абстракция (интерфейс)
class DatabaseInterface(ABC):
    @abstractmethod
    def save_user(self, user):
        pass

# Конкретные реализации
class MySQLDatabase(DatabaseInterface):
    def save_user(self, user):
        print(f'Saving {user} to MySQL')

class PostgreSQLDatabase(DatabaseInterface):
    def save_user(self, user):
        print(f'Saving {user} to PostgreSQL')

class MongoDBDatabase(DatabaseInterface):
    def save_user(self, user):
        print(f'Saving {user} to MongoDB')

# Бизнес-логика зависит от интерфейса, не от конкретной БД
class UserService:
    def __init__(self, database: DatabaseInterface):
        # Принимаем абстракцию, не конкретную БД!
        self.db = database
    
    def create_user(self, name):
        self.db.save_user(name)

# Теперь можем подставить любую БД
mysql_db = MySQLDatabase()
postgres_db = PostgreSQLDatabase()
mongo_db = MongoDBDatabase()

# UserService работает со всеми!
service1 = UserService(mysql_db)
service2 = UserService(postgres_db)
service3 = UserService(mongo_db)

service1.create_user('Alice')   # MySQL
service2.create_user('Bob')     # PostgreSQL
service3.create_user('Charlie') # MongoDB

Почему это нужно

1. Тестируемость

# С инверсией зависимостей, легко подставить mock

from unittest.mock import Mock

class TestUserService:
    def test_create_user(self):
        # Создаем fake БД для тестирования
        mock_db = Mock(spec=DatabaseInterface)
        
        service = UserService(mock_db)
        service.create_user('Alice')
        
        # Проверяем, что save_user вызвался
        mock_db.save_user.assert_called_once_with('Alice')

# Тест НЕ нуждается в настоящей БД!
# Быстрый, надежный, изолированный

2. Гибкость

# Можем легко менять БД без изменения UserService

def create_service(db_type='mysql'):
    if db_type == 'mysql':
        db = MySQLDatabase()
    elif db_type == 'postgres':
        db = PostgreSQLDatabase()
    else:
        db = MongoDBDatabase()
    
    return UserService(db)

# Бизнес-логика не зависит от выбора БД
service = create_service('postgres')
service.create_user('Alice')

3. Расширяемость

# Можем добавлять новые БД без изменения existing code

class FirebaseDatabase(DatabaseInterface):
    def save_user(self, user):
        print(f'Saving {user} to Firebase')

# UserService работает с Firebase автоматически!
firebase_db = FirebaseDatabase()
service = UserService(firebase_db)
service.create_user('David')

Реальный пример: Email сервис

from abc import ABC, abstractmethod

# Абстракция для отправки email
class EmailProvider(ABC):
    @abstractmethod
    def send(self, to: str, subject: str, body: str):
        pass

# Реализации
class SMTPEmailProvider(EmailProvider):
    def send(self, to: str, subject: str, body: str):
        print(f'Sending via SMTP to {to}')

class SendgridProvider(EmailProvider):
    def send(self, to: str, subject: str, body: str):
        print(f'Sending via Sendgrid to {to}')

class MailchimpProvider(EmailProvider):
    def send(self, to: str, subject: str, body: str):
        print(f'Sending via Mailchimp to {to}')

# Бизнес-логика
class NotificationService:
    def __init__(self, email_provider: EmailProvider):
        self.email = email_provider
    
    def notify_user_registered(self, user_email: str):
        self.email.send(
            to=user_email,
            subject='Welcome!',
            body='Thanks for registering'
        )
    
    def notify_user_password_reset(self, user_email: str):
        self.email.send(
            to=user_email,
            subject='Reset Password',
            body='Click to reset'
        )

# Тестирование с mock
class MockEmailProvider(EmailProvider):
    def __init__(self):
        self.sent_emails = []
    
    def send(self, to: str, subject: str, body: str):
        self.sent_emails.append({'to': to, 'subject': subject, 'body': body})

def test_notification_service():
    mock_email = MockEmailProvider()
    service = NotificationService(mock_email)
    
    service.notify_user_registered('alice@example.com')
    
    assert len(mock_email.sent_emails) == 1
    assert mock_email.sent_emails[0]['to'] == 'alice@example.com'
    print('Test passed!')

# Production
smtp_provider = SMTPEmailProvider()
service = NotificationService(smtp_provider)
service.notify_user_registered('alice@example.com')

test_notification_service()

Сравнение: с и без инверсии

❌ Без инверсии: сложно тестировать и менять

class PaymentProcessor:
    def __init__(self):
        self.stripe = StripeAPI()  # Жестко привязан
    
    def process_payment(self, amount):
        # Нельзя протестировать без Stripe API
        return self.stripe.charge(amount)

class OrderService:
    def __init__(self):
        self.payment = PaymentProcessor()
    
    def create_order(self, user, items):
        amount = sum(i.price for i in items)
        self.payment.process_payment(amount)  # Вызывается реальный API!

# Тест должен идти в Stripe:
service = OrderService()
service.create_order(user, items)  # Реальная транзакция!

✅ С инверсией: легко тестировать

class PaymentGateway(ABC):
    @abstractmethod
    def charge(self, amount: float) -> bool:
        pass

class StripeGateway(PaymentGateway):
    def charge(self, amount: float) -> bool:
        # Реальный API
        return True

class PaymentProcessor:
    def __init__(self, gateway: PaymentGateway):
        self.gateway = gateway
    
    def process_payment(self, amount):
        return self.gateway.charge(amount)

class OrderService:
    def __init__(self, payment: PaymentProcessor):
        self.payment = payment
    
    def create_order(self, user, items):
        amount = sum(i.price for i in items)
        return self.payment.process_payment(amount)

# Mock для тестирования
class MockGateway(PaymentGateway):
    def charge(self, amount: float) -> bool:
        print(f'Mock: charging {amount}')
        return True

def test_order_service():
    mock_gateway = MockGateway()
    payment = PaymentProcessor(mock_gateway)
    service = OrderService(payment)
    
    result = service.create_order(user, items)
    assert result is True
    print('Test passed, no real payment!')

test_order_service()  # Быстро, без реального API

DI Container (продвинутый подход)

class DIContainer:
    def __init__(self):
        self.services = {}
    
    def register(self, name: str, service):
        self.services[name] = service
    
    def get(self, name: str):
        return self.services[name]

# Конфигурация
container = DIContainer()
container.register('database', PostgreSQLDatabase())
container.register('email', SendgridProvider())

# Использование
db = container.get('database')
email = container.get('email')

user_service = UserService(db)
notif_service = NotificationService(email)

# Если нужно изменить реализацию — меняем только регистрацию
container.register('database', MySQLDatabase())
# Все сервисы автоматически используют новую реализацию

Практические советы

1. Определи интерфейс (абстракцию)

class RepositoryInterface(ABC):
    @abstractmethod
    def get_user(self, id: int):
        pass
    
    @abstractmethod
    def save_user(self, user):
        pass

2. Принимай через constructor

class UserService:
    def __init__(self, repo: RepositoryInterface):
        self.repo = repo  # Не создаем сами

3. Используй interfaces в type hints

def create_service(database: DatabaseInterface) -> UserService:
    # Явно показываем, что нужна абстракция
    return UserService(database)

4. Тестируй с mock

def test_service():
    mock_db = Mock(spec=DatabaseInterface)
    service = UserService(mock_db)
    # Тест

Итоговое правило

Зависимости должны течь от конкретного к абстрактному, а не наоборот.

Высокоуровневые модули (бизнес-логика) должны зависеть от абстракций, а низкоуровневые модули (детали реализации) должны реализовывать эти абстракции. Это делает код тестируемым, гибким и расширяемым.

Зачем нужна инверсия зависимостей? | PrepBro