跳到主內容

Dependency Injection (DI)

Dependency Injection (DI) 是一種設計模式,主要目的是將對象的依賴性注入到對象中,而不是在對象內部自行創建。這使得程式更加模組化、易於測試和維護。

Dependency Injection (DI) 的本質可以簡化為 "Object using Object",但更精確的描述是 "Object receiving its dependencies (other objects) from the outside"。這意味著一個物件依賴於另一個物件,但不是自己創建依賴,而是由外部提供"

Object using object" 是 DI 的核心,但 DI 的真正優勢在於外部提供依賴,實現鬆耦合與高靈活性。如果單純是物件使用其他物件,但依賴關係由內部創建,那麼就不是 DI,而是高耦合的設計。

這樣的設計方式強調物件之間的鬆耦合(low coupling),讓它們更容易測試和維護。

核心概念:

  1. 依賴性:一個對象需要的外部資源或服務。
  2. 注入:透過外部傳入(通常使用建構函式、屬性、或方法參數)。

PHP 範例

php
// 定義一個服務
class
classLogger Logger {    
public
function log($message) public{         functionecho log($message) {
        echo "Log: $message\message\n"; }
}

// 使用 Logger 的類別
class
classUserService UserService{     {
private
$logger;   private $logger; // 透過建構函式注入依賴
   

public
function public function __construct(__construct(Logger $logger)logger) {
       
        $this-this->logger = $logger;
 logger;     }    
public

function
createUser($username) {         public function createUser($username) {
        // 使用注入的Logger        
        $this-this->logger->log(log("User '$username'username' created.");    
 } }
}

// 手動注入
Logger
Logger $logger = new Logger(Logger();
$userService = new UserService(UserService($loggerlogger); $userService->createUser("Nathan");
$userService->createUser("Nathan");

結果

pgsql
Log: User 'Nathan' created. 
Log: User
'Nathan' created.

C# 範例

csharp
// 定義一個服務
public
publicclass class Logger {    
public
void Log(string public void Log(string message)message) {        
        Console.WriteLine($"Log: {message}");    
 } }
}

// 使用 Logger 的類別
public
publicclass class UserService {    
private
    private readonly Logger _logger;
   

    // 透過建構函式注入依賴    
public
    public UserService(UserService(Logger logger)logger) {
          _logger = logger;    
}
public void }
CreateUser(string

public void CreateUser(string username)username) {        
        // 使用注入的 Logger
        _logger.Log($"User '{username}' created.");    
 } }
}

// 程式進入點 
class Program {
   
static void static void Main(string[Main(string[] args)args) {
          Logger logger = new Logger();
          UserService userService = new UserService(logger);        
        userService.CreateUser("Nathan");    
 } }
}

結果

pgsql
Log:Log: User 'Nathan' created. 

與 "Object Using Object" 的差異

沒有 DI 的設計:

php
class 
classUserService UserService {    
private
$logger;     public privatefunction $logger;

    public function __construct(__construct() {        
        // 自行創建依賴        
        $this-this->logger = new Logger(Logger();
      }
   

public
function createUser($username) public function createUser($username) {        
        $this-this->logger->log(log("User '$username'username' created.");    
 } }
}

這樣的設計問題:

  1. 高耦合UserServiceLogger 被綁定在一起,無法輕易更換 Logger 的實現。
  2. 難以測試:如果需要模擬 Logger,就必須修改 UserService

使用 DI 的設計:

php
class 
classUserService UserService {    
private
$logger;     public privatefunction __construct($logger;
logger)

    public function __construct($logger) {        
        // 依賴由外部注入        
        $this-this->logger = $logger;
 logger;     }
   

public
function createUser($username) public{         function createUser($username) {
        $this-this->logger->log(log("User '$username'username' created.");    
 } }
}

這樣的設計優點:

  1. 低耦合UserService 只關注如何使用 Logger,不關心它的實現細節。
  2. 可替換性:可以輕鬆更換不同的 Logger 實現。
  3. 可測試性:可以注入假(mock)物件來模擬 Logger 的行為。

總結

  • Dependency Injection 的核心在於避免類別內部直接依賴其他類別,改為由外部提供依賴。
  • 在 PHP 和 C# 中,建構函式注入是最常見的方式。
  • DI 使代碼更加模組化、可測試,且方便更換依賴實現(例如更換 Logger 為不同的實作)