Skip to Content

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 Engine

Chúng ta làm:

class Car: def __init__(self, engine: Engine): # Engine được inject từ bên ngoài self.engine = engine

Tạ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 đề:

  1. Khó kiểm thử: Không thể mock EmailService để test
  2. Tight coupling: UserService phụ thuộc chặt vào EmailService
  3. Khó thay đổi: Muốn đổi sang SMSService phải sửa code UserService
  4. Khó cấu hình: Không thể cấu hình EmailService từ 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-injector
from 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 injector
from 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 = processor

2. 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_service

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 Injection class UserService: def __init__(self, logger: Logger): self.logger = logger

4. 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 = logger

Tổ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ạnhKhông DICó DI
CouplingTightLoose
TestingKhóDễ
FlexibilityThấpCao
ComplexityThấpTrung bình
BoilerplateÍtNhiề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