Dependency Injection
Dependency Injection là gì?
Phần tiêu đề “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:
Phần tiêu đề “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?
Phần tiêu đề “Tại sao cần Dependency Injection?”❌ Không dùng DI:
Phần tiêu đề “❌ 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ụngservice = UserService()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:
Phần tiêu đề “✅ Dùng DI:”from abc import ABC, abstractmethod
# 1. Định nghĩa interfaceclass NotificationService(ABC): @abstractmethod def send(self, to: str, message: str): pass
# 2. Implementationsclass 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àiclass 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 implementationemail_service = EmailService()user_service = UserService(email_service)
# Đổi sang SMSsms_service = SMSService()user_service = UserService(sms_service)user_service.register_user("Bình", "0901234567")
# Đổi sang Push Notificationpush_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
Phần tiêu đề “3 loại Dependency Injection”1. Constructor Injection (Phổ biến nhất)
Phần tiêu đề “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
Phần tiê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ụnggenerator = 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
Phần tiêu đề “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ụngnotifier = 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
Phần tiêu đề “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ần tiêu đề “Ví dụ không dùng DI Container:”# Phải tạo thủ công từng dependencylogger = 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:
Phần tiêu đề “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ụngfrom abc import ABC, abstractmethod
# Interfacesclass 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
# Implementationsclass 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}")
# Servicesclass 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 Containercontainer = DIContainer()
# Register servicescontainer.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 dependenciesuser_service = container.resolve(UserService)Thư viện DI Container phổ biến trong Python:
Phần tiêu đề “Thư viện DI Container phổ biến trong Python:”1. dependency-injector
Phần tiêu đề “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ụngcontainer = Container()container.config.database.host.from_value("localhost")container.config.database.port.from_value(3306)
user_service = container.user_service()2. injector
Phần tiêu đề “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
Phần tiêu đề “DI và Testing”DI giúp testing dễ dàng hơn rất nhiều:
Ví dụ test với mock:
Phần tiêu đề “Ví dụ test với mock:”from unittest.mock import Mockimport 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
# Assert mock_email_service.send.assert_called_once_with( "Welcome!" ) mock_logger.log.assert_called()Khi nào nên dùng DI?
Phần tiêu đề “Khi nào nên dùng DI?”✅ Nên dùng khi:
Phần tiêu đề “✅ 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:
Phần tiêu đề “⚠️ 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
Phần tiêu đề “Best Practices”1. Ưu tiên Constructor Injection
Phần tiêu đề “1. Ưu tiên Constructor Injection”# ✅ Tốtclass OrderService: def __init__(self, payment_processor: PaymentProcessor): self.payment_processor = payment_processor
# ❌ Tránhclass 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
Phần tiêu đề “2. Inject interface, không phải concrete class”# ✅ Tốt - Inject abstractclass UserService: def __init__(self, notifier: Notifier): # Abstract self.notifier = notifier
# ❌ Tránh - Inject concreteclass UserService: def __init__(self, email_service: EmailService): # Concrete self.email_service = email_service3. Tránh Service Locator anti-pattern
Phần tiêu đề “3. 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 Injectionclass UserService: def __init__(self, logger: Logger): self.logger = logger4. Giữ dependencies ở mức tối thiểu
Phần tiêu đề “4. Giữ dependencies ở mức tối thiểu”# ❌ Quá nhiều dependenciesclass UserService: def __init__(self, dep1, dep2, dep3, dep4, dep5, dep6, dep7): # Quá nhiều - có thể vi phạm SRP pass
# ✅ Ít dependenciesclass UserService: def __init__(self, user_repository: UserRepository, logger: Logger): self.user_repository = user_repository self.logger = loggerTổng kết
Phần tiêu đề “Tổng kết”Lợi ích của DI:
Phần tiêu đề “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:
Phần tiêu đề “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!