在實際開發中,只要內容開始複雜,使用到的類別越來越多,就會慢慢發現,為什麼我只是要抽換或更改其中的類別,卻會限制這麼多以及需要大量改動。 這時我才注意到這個主題的重要性,但是在了解依賴注入之前讓我們先談談甚麼是控制反轉(IoC)
。
控制反轉 控制反轉(Inversion of Control,簡稱 IoC),它指的是將應用程式的控制權轉移到外部元件或框架,以實現更大程度的可配置性和可擴展性。IoC強調的是將依賴管理的責任從應用程式內部移出,通常透過依賴注入來實現。IoC有助於改善軟體架構,降低耦合性,提高可測試性,並使系統更容易配置和調整。
依賴注入 依賴注入(Dependency Injection,簡稱 DI)是一種軟體設計模式,主要用途於解決程式碼中的相依性問題。在傳統的程式設計中,一個類通常負責創建和管理其所需的相依物件。然而,這種方式容易導致高耦合的程式碼,難以測試和維護。
從以上兩點說明可以知道,控制反轉是一種解除依賴降低於耦合性的概念,至於依賴注入則是實現該概念其中一種方法。
範例說明 有一個紀錄Log的服務,以下為具體實現服務的類別FileLogger和DatabaseLogger。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 interface Logger { public function log ($message ) ; } class FileLogger implements Logger { private $filename ; public function __construct ($filename ) { $this ->filename = $filename ; } public function log ($message ) { file_put_contents ($this ->filename, $message . PHP_EOL, FILE_APPEND); } } class DatabaseLogger implements Logger { public function log ($message ) { } }
假設沒有使用依賴注入,在服務內部直接實例兩種具體物件,高度耦合內部物件,假設要新增新類別,就會需要改動內部邏輯,非常不方便。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class EventService { private $fileLogger ; private $databaseLogger ; public function __construct ( ) { $this ->fileLogger = new FileLogger ("app.log" ); $this ->databaseLogger = new DatabaseLogger ("app.log" ); } public function processEvent ($eventName ) { $this ->fileLogger->log ("Event processed: " . $eventName ); $this ->databaseLogger->log ("Event processed: " . $eventName ); } } $eventServiceWithFileLogger = new EventService ();$eventServiceWithFileLogger ->processEvent ("Event 1" );
這時加入了依賴注入,把實例的行為移到外部,讓服務不去控制內部類別,而是改成外部去控制,這個概念就是控制反轉
,這樣就能自由加入需要的類別,大大增加擴展性與測試性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class EventService { private $logger ; public function __construct (Logger $logger ) { $this ->logger = $logger ; } public function processEvent ($eventName ) { $this ->logger->log ("Event processed: " . $eventName ); } } $fileLogger = new FileLogger ("app.log" );$eventServiceWithFileLogger = new EventService ($fileLogger );$databaseLogger = new DatabaseLogger ();$eventServiceWithDatabaseLogger = new EventService ($databaseLogger );$eventServiceWithFileLogger ->processEvent ("Event 1" );$eventServiceWithDatabaseLogger ->processEvent ("Event 2" );
優點 優點就如同上面說明與範例可得知,有以下幾點:
測試性提升: 通過依賴注入,我們可以將虛擬的相依物件注入到類中,從而使單元測試更加輕鬆。這種方式下,我們可以專注於測試類的行為,而不需要實際創建相依物件。
鬆散耦合: 依賴注入降低了類之間的耦合度,使不同模塊更加獨立。這使得程式碼更加靈活和易於修改。
可維護性提升: 依賴注入使程式碼的組織更加清晰,類不再負責管理相依物件,使程式碼更易於理解和維護。
缺點 然而,依賴注入也並非沒有缺點,以下是一些可能出現的問題:
循環依賴:在使用依賴注入時,有時可能會出現循環依賴的情況,即不同的類彼此相互依賴,形成一個閉環。這種情況下,必須謹慎設計類之間的相依關係,以避免循環依賴導致程式碼難以理解和維護。
如同以下簡單範例,A實例需要B,B實例需要A,相互依賴,這樣會導致程式發生無窮迴圈,雖然這個缺點相對比較難發生,但是如果應用程式越來越龐大,以及PHP的自動加載功能,也是有可能發生此問題。
1 2 3 4 5 6 7 8 9 10 11 12 13 class A { public function __construct (B $b ) { } } class B { public function __construct (A $a ) { } }
過度注入: 過度注入指的是在類的建構子中注入過多的相依物件,導致建構子參數過多,影響程式碼的可讀性。過度注入可能在某些情況下使程式碼變得複雜。
如同以下簡單範例,一個類別注入多種類別,導致建構函式變得冗長且難以管理,特別是如果未來需要添加更多依賴時,程式碼將變得更複雜。
1 2 3 4 5 6 7 8 9 10 class Order { public function __construct ( Database $database , Logger $logger , EmailService $emailService , PaymentGateway $paymentGateway ) { } }
總結 如果想要寫出高擴充、高測試性、好維護的服務,其中依賴注入是一個相當重要的概念,這也是為什麼Laravel以及許多框架這麼愛用的原因。推薦給大家這個好用的設計模式!!