← Назад к вопросам
Приведи пример применения принципа Dependency Inversion
1.8 Middle🔥 171 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример применения принципа Dependency Inversion
Dependency Inversion Principle (DIP) — это последний принцип SOLID. Он утверждает, что высокоуровневые модули не должны зависеть от низкоуровневых модулей. Обе должны зависеть от абстракций.
Суть принципа
❌ НЕПРАВИЛЬНО (Tight Coupling):
HighLevelModule → ConcreteImplementation
✅ ПРАВИЛЬНО (Loose Coupling):
HighLevelModule → Abstraction ← ConcreteImplementation
Пример 1: Email рассылка
# ❌ БЕЗ Dependency Inversion
# Класс зависит от конкретной реализации EmailService
class EmailService:
def send(self, email: str, message: str) -> bool:
print(f"Отправка на {email}: {message}")
return True
class OrderProcessor:
def __init__(self):
self.email_service = EmailService() # ❌ Жёсткая зависимость!
def process_order(self, order_id: int, customer_email: str):
# Обработка заказа
order = self.get_order(order_id)
# Отправить уведомление
self.email_service.send(
customer_email,
f"Заказ {order_id} обработан"
)
# Проблемы:
# 1. Нельзя тестировать без реального EmailService
# 2. Нельзя заменить на SMS или Slack уведомление
# 3. Если EmailService упадёт, упадёт и OrderProcessor
# ✅ С Dependency Inversion
# OrderProcessor зависит от абстракции NotificationService
from abc import ABC, abstractmethod
from typing import Protocol
# Определяем контракт (абстракцию)
class NotificationService(ABC):
@abstractmethod
def send(self, recipient: str, message: str) -> bool:
pass
# Конкретные реализации
class EmailService(NotificationService):
def send(self, email: str, message: str) -> bool:
print(f"📧 Email отправлен на {email}: {message}")
return True
class SMSService(NotificationService):
def send(self, phone: str, message: str) -> bool:
print(f"📱 SMS отправлено на {phone}: {message}")
return True
class SlackService(NotificationService):
def send(self, channel: str, message: str) -> bool:
print(f"💬 Slack сообщение в {channel}: {message}")
return True
# Высокоуровневый модуль зависит от абстракции
class OrderProcessor:
def __init__(self, notification_service: NotificationService):
# ✅ Инъекция зависимости через конструктор
self.notification_service = notification_service
def process_order(self, order_id: int, recipient: str):
# Обработка заказа
order = self.get_order(order_id)
# Используем абстракцию, не конкретную реализацию
self.notification_service.send(
recipient,
f"Заказ {order_id} обработан"
)
def get_order(self, order_id: int):
return {"id": order_id, "status": "processed"}
# Использование
print("=== С Email ===")
email_service = EmailService()
processor = OrderProcessor(email_service)
processor.process_order(123, "user@example.com")
print("\n=== С SMS ===")
sms_service = SMSService()
processor = OrderProcessor(sms_service)
processor.process_order(123, "+79991234567")
print("\n=== Со Slack ===")
slack_service = SlackService()
processor = OrderProcessor(slack_service)
processor.process_order(123, "#notifications")
Пример 2: Работа с БД
# ❌ БЕЗ DIP: UserService зависит от конкретной БД
class MySQLDatabase:
def query(self, sql: str):
# Реальное подключение к MySQL
pass
class UserService:
def __init__(self):
self.db = MySQLDatabase() # ❌ Жёсткая связь!
def get_user(self, user_id: int):
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
# Если нужно перейти на PostgreSQL или MongoDB — нужно переписывать UserService!
# ✅ С DIP: UserService зависит от абстракции
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any
# Абстракция
class Database(ABC):
@abstractmethod
def query(self, sql: str) -> Any:
pass
@abstractmethod
def insert(self, sql: str, params: Dict) -> int:
pass
@abstractmethod
def update(self, sql: str, params: Dict) -> int:
pass
# Конкретные реализации
class MySQLDatabase(Database):
def __init__(self, host: str, user: str, password: str):
self.host = host
self.user = user
self.password = password
def query(self, sql: str) -> Any:
# Подключение к MySQL
print(f"[MySQL] Выполняю: {sql}")
return {"id": 1, "name": "John"}
def insert(self, sql: str, params: Dict) -> int:
print(f"[MySQL] INSERT: {sql} с {params}")
return 1
def update(self, sql: str, params: Dict) -> int:
print(f"[MySQL] UPDATE: {sql} с {params}")
return 1
class PostgreSQLDatabase(Database):
def __init__(self, host: str, user: str, password: str):
self.host = host
self.user = user
self.password = password
def query(self, sql: str) -> Any:
print(f"[PostgreSQL] Выполняю: {sql}")
return {"id": 1, "name": "John"}
def insert(self, sql: str, params: Dict) -> int:
print(f"[PostgreSQL] INSERT: {sql} с {params}")
return 1
def update(self, sql: str, params: Dict) -> int:
print(f"[PostgreSQL] UPDATE: {sql} с {params}")
return 1
class MongoDBDatabase(Database):
def __init__(self, connection_string: str):
self.connection_string = connection_string
def query(self, sql: str) -> Any:
print(f"[MongoDB] Выполняю: {sql}")
return {"_id": "1", "name": "John"}
def insert(self, sql: str, params: Dict) -> int:
print(f"[MongoDB] INSERT: {sql} с {params}")
return 1
def update(self, sql: str, params: Dict) -> int:
print(f"[MongoDB] UPDATE: {sql} с {params}")
return 1
# Высокоуровневый модуль зависит от абстракции
class UserService:
def __init__(self, db: Database): # ✅ Инъекция абстракции
self.db = db
def get_user(self, user_id: int) -> Optional[Dict]:
return self.db.query(f"SELECT * FROM users WHERE id = {user_id}")
def create_user(self, name: str, email: str) -> int:
return self.db.insert(
"INSERT INTO users (name, email) VALUES (?, ?)",
{"name": name, "email": email}
)
def update_user(self, user_id: int, name: str) -> int:
return self.db.update(
"UPDATE users SET name = ? WHERE id = ?",
{"name": name, "id": user_id}
)
# Тестирование с mock БД
class MockDatabase(Database):
def query(self, sql: str) -> Any:
return {"id": 1, "name": "Test User"}
def insert(self, sql: str, params: Dict) -> int:
return 999
def update(self, sql: str, params: Dict) -> int:
return 1
# Использование в тестах
def test_user_service():
mock_db = MockDatabase()
service = UserService(mock_db)
user = service.get_user(1)
assert user["name"] == "Test User"
print("✅ Тест пройден")
# Использование в production
print("=== MySQL ===")
mysql_db = MySQLDatabase(host="localhost", user="root", password="pass")
user_service = UserService(mysql_db)
user = user_service.get_user(1)
print("\n=== PostgreSQL ===")
pg_db = PostgreSQLDatabase(host="localhost", user="postgres", password="pass")
user_service = UserService(pg_db)
user = user_service.get_user(1)
print("\n=== MongoDB ===")
mongo_db = MongoDBDatabase("mongodb://localhost:27017/mydb")
user_service = UserService(mongo_db)
user = user_service.get_user(1)
print("\n=== Тестирование ===")
test_user_service()
Пример 3: Логирование
from abc import ABC, abstractmethod
import logging
# Абстракция
class Logger(ABC):
@abstractmethod
def log(self, level: str, message: str) -> None:
pass
# Конкретные реализации
class FileLogger(Logger):
def __init__(self, filename: str):
self.filename = filename
def log(self, level: str, message: str) -> None:
with open(self.filename, a) as f:
f.write(f"[{level}] {message}\n")
class ConsoleLogger(Logger):
def log(self, level: str, message: str) -> None:
print(f"[{level}] {message}")
class SentryLogger(Logger):
def log(self, level: str, message: str) -> None:
# Отправить в Sentry
if level == "ERROR":
print(f"[SENTRY] Отправил ошибку: {message}")
# Бизнес-логика
class PaymentProcessor:
def __init__(self, logger: Logger):
self.logger = logger
def process_payment(self, amount: float) -> bool:
try:
self.logger.log("INFO", f"Обработка платежа на сумму {amount}")
# Обработка платежа
self.logger.log("INFO", f"Платёж успешен")
return True
except Exception as e:
self.logger.log("ERROR", f"Ошибка платежа: {str(e)}")
return False
# Использование
print("=== Console Logging ===")
console_logger = ConsoleLogger()
processor = PaymentProcessor(console_logger)
processor.process_payment(100.0)
print("\n=== File Logging ===")
file_logger = FileLogger("/tmp/app.log")
processor = PaymentProcessor(file_logger)
processor.process_payment(50.0)
Пример 4: Dependency Injection Container
from abc import ABC, abstractmethod
from typing import Type, TypeVar, Dict, Any
T = TypeVar(T)
# Контейнер зависимостей
class DIContainer:
def __init__(self):
self._services: Dict[Type, Any] = {}
self._factories: Dict[Type, callable] = {}
def register(self, interface: Type[T], implementation: Type[T]) -> None:
"""Регистрирует реализацию для интерфейса"""
self._services[interface] = implementation
def register_factory(self, interface: Type[T], factory: callable) -> None:
"""Регистрирует фабрику для интерфейса"""
self._factories[interface] = factory
def resolve(self, interface: Type[T]) -> T:
"""Разрешает зависимость"""
if interface in self._factories:
return self._factories[interface]()
if interface in self._services:
implementation = self._services[interface]
return implementation()
raise ValueError(f"No registration for {interface}")
# Определяем интерфейсы
class NotificationService(ABC):
@abstractmethod
def send(self, recipient: str, message: str) -> bool:
pass
class UserRepository(ABC):
@abstractmethod
def get_user(self, user_id: int):
pass
# Реализации
class EmailNotificationService(NotificationService):
def send(self, recipient: str, message: str) -> bool:
print(f"📧 Email: {message}")
return True
class MockUserRepository(UserRepository):
def get_user(self, user_id: int):
return {"id": user_id, "name": "Test User"}
# Бизнес-логика
class UserRegistrationService:
def __init__(self, notification: NotificationService, repository: UserRepository):
self.notification = notification
self.repository = repository
def register(self, user_id: int, email: str):
user = self.repository.get_user(user_id)
self.notification.send(email, f"Добро пожаловать, {user[name]}!")
# Конфигурация DI контейнера
container = DIContainer()
container.register(NotificationService, EmailNotificationService)
container.register(UserRepository, MockUserRepository)
# Использование
notification = container.resolve(NotificationService)
repository = container.resolve(UserRepository)
service = UserRegistrationService(notification, repository)
service.register(1, "user@example.com")
Преимущества DIP
# ✅ Тестирование
class TestUserService:
def test_with_mock_db(self):
mock_db = MockDatabase()
service = UserService(mock_db)
user = service.get_user(1)
assert user is not None
# ✅ Расширяемость
# Можно добавить новую реализацию без изменения UserService
class RedisCache(Database):
# Новая реализация
pass
# ✅ Гибкость конфигурации
if config.get(USE_MYSQL):
db = MySQLDatabase(...)
elif config.get(USE_POSTGRESQL):
db = PostgreSQLDatabase(...)
else:
db = MockDatabase()
service = UserService(db)
# ✅ Разделение ответственности
# UserService отвечает за бизнес-логику
# Database - за работу с хранилищем
# Они не знают о деталях реализации друг друга
Антипаттерны
# ❌ НЕПРАВИЛЬНО: new внутри класса
class BadService:
def __init__(self):
self.db = MySQLDatabase() # Жёсткая зависимость!
self.logger = FileLogger() # Жёсткая зависимость!
self.cache = RedisCache() # Жёсткая зависимость!
# ✅ ПРАВИЛЬНО: инъекция зависимостей
class GoodService:
def __init__(
self,
db: Database,
logger: Logger,
cache: Cache
):
self.db = db
self.logger = logger
self.cache = cache
Заключение
Dependency Inversion Principle помогает создавать:
- Тестируемый код — легко подменять реальные сервисы на mock-и
- Гибкий код — просто менять реализации
- Слабосвязанный код — модули не знают друг о друге
- Масштабируемый код — легко добавлять новые реализации
Ключевая идея: зависимости должны указывать вверх (от конкретного к абстрактному), а не вниз.