Dependency Injection (DI)
Dependency Injection là gì?
Dependency Injection (DI) là một kỹ thuật thiết kế trong đó một object nhận các dependencies của nó từ bên ngoài thay vì tự tạo ra chúng.
Ví dụ đơn giản:
Thay vì:
class Car:
def __init__(self):
self.engine = Engine() # Car tự tạo EngineChúng ta làm:
class Car:
def __init__(self, engine: Engine): # Engine được inject từ bên ngoài
self.engine = engineTại sao cần Dependency Injection?
❌ Không dùng DI:
class EmailService:
def send_email(self, to: str, message: str):
print(f"Sending email to {to}: {message}")
class UserService:
def __init__(self):
# UserService tự tạo EmailService
self.email_service = EmailService()
def register_user(self, username: str, email: str):
# Business logic
print(f"Registering user: {username}")
# Gửi email
self.email_service.send_email(email, "Welcome!")
# Sử dụng
service = UserService()
service.register_user("An", "an@example.com")Vấn đề:
- Khó kiểm thử: Không thể mock
EmailServiceđể test - Tight coupling:
UserServicephụ thuộc chặt vàoEmailService - Khó thay đổi: Muốn đổi sang
SMSServicephải sửa codeUserService - Khó cấu hình: Không thể cấu hình
EmailServicetừ bên ngoài
✅ Dùng DI:
from abc import ABC, abstractmethod
# 1. Định nghĩa interface
class NotificationService(ABC):
@abstractmethod
def send(self, to: str, message: str):
pass
# 2. Implementations
class EmailService(NotificationService):
def send(self, to: str, message: str):
print(f"Sending email to {to}: {message}")
class SMSService(NotificationService):
def send(self, to: str, message: str):
print(f"Sending SMS to {to}: {message}")
class PushNotificationService(NotificationService):
def send(self, to: str, message: str):
print(f"Sending push notification to {to}: {message}")
# 3. UserService nhận dependency từ bên ngoài
class UserService:
def __init__(self, notification_service: NotificationService):
self.notification_service = notification_service
def register_user(self, username: str, contact: str):
print(f"Registering user: {username}")
self.notification_service.send(contact, "Welcome!")
# Sử dụng - Dễ dàng thay đổi implementation
email_service = EmailService()
user_service = UserService(email_service)
user_service.register_user("An", "an@example.com")
# Đổi sang SMS
sms_service = SMSService()
user_service = UserService(sms_service)
user_service.register_user("Bình", "0901234567")
# Đổi sang Push Notification
push_service = PushNotificationService()
user_service = UserService(push_service)
user_service.register_user("Chi", "device_token_123")Lợi ích:
- ✅ Dễ kiểm thử với mock
- ✅ Loose coupling
- ✅ Dễ thay đổi implementation
- ✅ Linh hoạt cấu hình
3 loại Dependency Injection
1. Constructor Injection (Phổ biến nhất)
Dependencies được truyền qua constructor:
class OrderService:
def __init__(
self,
payment_processor: PaymentProcessor,
email_service: EmailService,
logger: Logger
):
self.payment_processor = payment_processor
self.email_service = email_service
self.logger = logger
def process_order(self, order: Order):
self.logger.log("Processing order")
self.payment_processor.process(order)
self.email_service.send_confirmation(order)Ưu điểm:
- Dependencies bắt buộc phải có
- Immutable sau khi khởi tạo
- Rõ ràng, dễ hiểu
2. Setter Injection
Dependencies được set thông qua setter methods:
class ReportGenerator:
def __init__(self):
self._data_source = None
self._formatter = None
def set_data_source(self, data_source: DataSource):
self._data_source = data_source
def set_formatter(self, formatter: Formatter):
self._formatter = formatter
def generate(self):
if not self._data_source or not self._formatter:
raise ValueError("Dependencies not set")
data = self._data_source.get_data()
return self._formatter.format(data)
# Sử dụng
generator = ReportGenerator()
generator.set_data_source(DatabaseDataSource())
generator.set_formatter(PDFFormatter())
report = generator.generate()Ưu điểm:
- Dependencies optional
- Có thể thay đổi sau khi khởi tạo
Nhược điểm:
- Object có thể ở trạng thái không hợp lệ
3. Interface Injection
Dependencies được inject qua interface method:
from abc import ABC, abstractmethod
class EmailServiceInjectable(ABC):
@abstractmethod
def inject_email_service(self, email_service: EmailService):
pass
class UserNotifier(EmailServiceInjectable):
def __init__(self):
self._email_service = None
def inject_email_service(self, email_service: EmailService):
self._email_service = email_service
def notify(self, user: User):
if not self._email_service:
raise ValueError("EmailService not injected")
self._email_service.send(user.email, "Notification")
# Sử dụng
notifier = UserNotifier()
notifier.inject_email_service(EmailService())Ưu điểm:
- Rõ ràng về contract
Nhược điểm:
- Phức tạp hơn
- Ít được sử dụng trong Python
DI Container
DI Container là một object quản lý việc tạo và inject dependencies tự động.
Ví dụ không dùng DI Container:
# Phải tạo thủ công từng dependency
logger = FileLogger("app.log")
database = MySQLDatabase("localhost", "mydb")
email_service = SMTPEmailService("smtp.gmail.com", 587)
user_repository = UserRepository(database, logger)
order_repository = OrderRepository(database, logger)
user_service = UserService(user_repository, email_service, logger)
order_service = OrderService(order_repository, email_service, logger)Vấn đề:
- Nhiều boilerplate code
- Khó quản lý khi có nhiều dependencies
- Dễ quên inject dependency
Ví dụ dùng DI Container:
from typing import Type, Callable, Dict, Any
class DIContainer:
def __init__(self):
self._services: Dict[Type, Callable] = {}
self._singletons: Dict[Type, Any] = {}
def register(self, interface: Type, implementation: Callable, singleton: bool = False):
"""Register một service."""
self._services[interface] = implementation
if singleton:
self._singletons[interface] = None
def resolve(self, interface: Type):
"""Resolve và tạo instance của service."""
# Nếu là singleton và đã tạo rồi, return instance cũ
if interface in self._singletons:
if self._singletons[interface] is None:
self._singletons[interface] = self._create_instance(interface)
return self._singletons[interface]
# Tạo instance mới
return self._create_instance(interface)
def _create_instance(self, interface: Type):
"""Tạo instance và inject dependencies."""
implementation = self._services.get(interface)
if implementation is None:
raise ValueError(f"Service {interface} not registered")
# Lấy dependencies từ type hints
import inspect
signature = inspect.signature(implementation)
dependencies = {}
for param_name, param in signature.parameters.items():
if param.annotation != inspect.Parameter.empty:
# Recursively resolve dependencies
dependencies[param_name] = self.resolve(param.annotation)
return implementation(**dependencies)
# Sử dụng
from abc import ABC, abstractmethod
# Interfaces
class Logger(ABC):
@abstractmethod
def log(self, message: str): pass
class Database(ABC):
@abstractmethod
def query(self, sql: str): pass
class EmailService(ABC):
@abstractmethod
def send(self, to: str, message: str): pass
# Implementations
class FileLogger(Logger):
def __init__(self):
self.file = "app.log"
def log(self, message: str):
print(f"Logging to {self.file}: {message}")
class MySQLDatabase(Database):
def query(self, sql: str):
print(f"Executing SQL: {sql}")
class SMTPEmailService(EmailService):
def send(self, to: str, message: str):
print(f"Sending email to {to}: {message}")
# Services
class UserRepository:
def __init__(self, database: Database, logger: Logger):
self.database = database
self.logger = logger
def get_user(self, user_id: int):
self.logger.log(f"Getting user {user_id}")
self.database.query(f"SELECT * FROM users WHERE id = {user_id}")
class UserService:
def __init__(self, user_repository: UserRepository, email_service: EmailService, logger: Logger):
self.user_repository = user_repository
self.email_service = email_service
self.logger = logger
def register_user(self, username: str, email: str):
self.logger.log(f"Registering user {username}")
# Save user...
self.email_service.send(email, "Welcome!")
# Setup DI Container
container = DIContainer()
# Register services
container.register(Logger, FileLogger, singleton=True)
container.register(Database, MySQLDatabase, singleton=True)
container.register(EmailService, SMTPEmailService, singleton=True)
container.register(UserRepository, UserRepository)
container.register(UserService, UserService)
# Resolve - Container tự động inject dependencies
user_service = container.resolve(UserService)
user_service.register_user("An", "an@example.com")Thư viện DI Container phổ biến trong Python:
1. dependency-injector
pip install dependency-injectorfrom dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
logger = providers.Singleton(FileLogger)
database = providers.Singleton(
MySQLDatabase,
host=config.database.host,
port=config.database.port
)
email_service = providers.Factory(SMTPEmailService)
user_repository = providers.Factory(
UserRepository,
database=database,
logger=logger
)
user_service = providers.Factory(
UserService,
user_repository=user_repository,
email_service=email_service,
logger=logger
)
# Sử dụng
container = Container()
container.config.database.host.from_value("localhost")
container.config.database.port.from_value(3306)
user_service = container.user_service()
user_service.register_user("An", "an@example.com")2. injector
pip install injectorfrom injector import Injector, inject, singleton
class UserService:
@inject
def __init__(self, logger: Logger, email_service: EmailService):
self.logger = logger
self.email_service = email_service
def configure(binder):
binder.bind(Logger, to=FileLogger, scope=singleton)
binder.bind(EmailService, to=SMTPEmailService)
injector = Injector([configure])
user_service = injector.get(UserService)DI và Testing
DI giúp testing dễ dàng hơn rất nhiều:
Ví dụ test với mock:
from unittest.mock import Mock
import unittest
class TestUserService(unittest.TestCase):
def test_register_user_sends_email(self):
# Arrange
mock_email_service = Mock(spec=EmailService)
mock_logger = Mock(spec=Logger)
mock_repository = Mock(spec=UserRepository)
user_service = UserService(
mock_repository,
mock_email_service,
mock_logger
)
# Act
user_service.register_user("An", "an@example.com")
# Assert
mock_email_service.send.assert_called_once_with(
"an@example.com",
"Welcome!"
)
mock_logger.log.assert_called()Khi nào nên dùng DI?
✅ Nên dùng khi:
- Cần kiểm thử code với mock
- Muốn dễ dàng thay đổi implementation
- Code phức tạp với nhiều dependencies
- Làm việc nhóm, cần module hóa
- Dự án lớn, dài hạn
⚠️ Có thể không cần khi:
- Script đơn giản, nhỏ
- Prototype nhanh
- Không cần kiểm thử
- Chỉ có một implementation duy nhất
Best Practices
1. Ưu tiên Constructor Injection
# ✅ Tốt
class OrderService:
def __init__(self, payment_processor: PaymentProcessor):
self.payment_processor = payment_processor
# ❌ Tránh
class OrderService:
def __init__(self):
self.payment_processor = None
def set_payment_processor(self, processor):
self.payment_processor = processor2. Inject interface, không phải concrete class
# ✅ Tốt - Inject abstract
class UserService:
def __init__(self, notifier: Notifier): # Abstract
self.notifier = notifier
# ❌ Tránh - Inject concrete
class UserService:
def __init__(self, email_service: EmailService): # Concrete
self.email_service = email_service3. Tránh Service Locator anti-pattern
# ❌ Service Locator (anti-pattern)
class ServiceLocator:
_services = {}
@classmethod
def get(cls, service_type):
return cls._services[service_type]
class UserService:
def __init__(self):
# Tìm dependency từ service locator
self.logger = ServiceLocator.get(Logger)
# ✅ Dependency Injection
class UserService:
def __init__(self, logger: Logger):
self.logger = logger4. Giữ dependencies ở mức tối thiểu
# ❌ Quá nhiều dependencies
class UserService:
def __init__(self, dep1, dep2, dep3, dep4, dep5, dep6, dep7):
# Quá nhiều - có thể vi phạm SRP
pass
# ✅ Ít dependencies
class UserService:
def __init__(self, user_repository: UserRepository, logger: Logger):
self.user_repository = user_repository
self.logger = loggerTổng kết
Lợi ích của DI:
- ✅ Loose coupling: Module độc lập
- ✅ Testability: Dễ test với mock
- ✅ Flexibility: Dễ thay đổi implementation
- ✅ Maintainability: Dễ bảo trì
- ✅ Reusability: Code tái sử dụng được
So sánh DI vs không DI:
| Khía cạnh | Không DI | Có DI |
|---|---|---|
| Coupling | Tight | Loose |
| Testing | Khó | Dễ |
| Flexibility | Thấp | Cao |
| Complexity | Thấp | Trung bình |
| Boilerplate | Ít | Nhiều hơn |
Dependency Injection là một kỹ thuật quan trọng trong thiết kế phần mềm hiện đại. Kết hợp với SOLID principles, DI giúp bạn xây dựng code chất lượng cao!
Last updated on
Python