🧩 Dependency Injection (DI) Pattern
✅ Intent
- Instead of creating dependencies internally, inject them from outside
- Shifts the responsibility of "which implementation to use" outside the class, improving flexibility
✅ Motivation
- Makes it easy to swap in mocks or stubs for testing
- Allows changing the configuration of services without modifying implementation code
✅ When to Use
- In applications that support DI containers
- When you want to explicitly manage class dependencies
✅ Code Example
- TypeScript
- PHP
- Python
// インターフェースで依存先を抽象化(任意)
interface Notifier {
send(email: string, message: string): void;
}
// 実装クラス
class Mailer implements Notifier {
send(email: string, message: string): void {
console.log(`送信しました: ${email} → ${message}`);
}
}
// クライアントクラス
class UserService {
constructor(private notifier: Notifier) {}
registerUser(email: string): void {
console.log(`ユーザー登録: ${email}`);
this.notifier.send(email, "ようこそ!");
}
}
// 利用例(依存を注入)
const mailer = new Mailer();
const service = new UserService(mailer);
service.registerUser("user@example.com");
<?php
interface Notifier {
public function send(string $email, string $message): void;
}
class Mailer implements Notifier {
public function send(string $email, string $message): void {
echo "送信しました: {$email} → {$message}\n";
}
}
class UserService {
public function __construct(private Notifier $notifier) {}
public function registerUser(string $email): void {
echo "ユーザー登録: {$email}\n";
$this->notifier->send($email, "ようこそ!");
}
}
// 利用例(依存を注入)
$mailer = new Mailer();
$service = new UserService($mailer);
$service->registerUser("user@example.com");
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, email: str, message: str):
pass
class Mailer(Notifier):
def send(self, email: str, message: str):
print(f"送信しました: {email} → {message}")
class UserService:
def __init__(self, notifier: Notifier):
self.notifier = notifier
def register_user(self, email: str):
print(f"ユーザー登録: {email}")
self.notifier.send(email, "ようこそ!")
# 利用例
mailer = Mailer()
service = UserService(mailer)
service.register_user("user@example.com")
✅ Explanation
This code uses Dependency Injection (DI) to design a system where UserService
receives its dependency (Notifier
) from the outside.
The DI pattern helps achieve loose coupling by decoupling a class from the concrete implementation of its dependencies.
1. Overview of Dependency Injection
-
Interface: Defines a common abstraction for the dependency
- Represented by
Notifier
in this code
- Represented by
-
Concrete Implementation: Implements the interface and provides actual behavior
- Represented by
Mailer
- Represented by
-
Client: Relies on the interface and receives the implementation externally
- Represented by
UserService
- Represented by
2. Key Classes and Their Roles
-
Notifier
- The interface representing a generic notification mechanism
- Declares
send(email: string, message: string): void
-
Mailer
- A concrete class that implements
Notifier
- Sends notifications via email
- A concrete class that implements
-
UserService
- The client class that depends on
Notifier
- Receives the dependency via constructor injection
- After registering a user, it sends a notification using the injected
Notifier
- The client class that depends on
3. UML Class Diagram
4. Benefits of Dependency Injection
- Loose Coupling:
UserService
does not depend on a specific implementation likeMailer
, making it easy to replace the dependency - Testability: Easily inject mocks or stubs, simplifying unit testing
- Extensibility: New notification methods (e.g., SMS) can be added by implementing
Notifier
without modifying existing code
This design improves the flexibility and maintainability of code
by managing dependencies externally. It's especially effective when dependencies may change or when improving testability is a priority.