PHP
Wstrzykiwanie zależności
Szukaj…
Wprowadzenie
Wtrysk Konstruktora
Obiekty często zależą od innych obiektów. Zamiast tworzyć zależność w konstruktorze, zależność należy przekazać do konstruktora jako parametr. To gwarantuje, że nie ma ścisłego połączenia między obiektami i umożliwia zmianę zależności po utworzeniu instancji klasy. Ma to szereg zalet, w tym ułatwienie odczytu kodu poprzez wyraźne zależności, a także uproszczenie testowania, ponieważ zależności można łatwiej zamieniać i wyśmiewać.
W poniższym przykładzie Component
będzie zależał od wystąpienia programu Logger
, ale go nie utworzy. Zamiast tego wymaga przekazania jednego argumentu do konstruktora.
interface Logger {
public function log(string $message);
}
class Component {
private $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
}
Bez wstrzykiwania zależności kod prawdopodobnie wyglądałby podobnie do:
class Component {
private $logger;
public function __construct() {
$this->logger = new FooLogger();
}
}
Użycie new
do utworzenia nowych obiektów w konstruktorze wskazuje, że wstrzyknięcie zależności nie zostało użyte (lub zostało użyte niepełne), a kod jest ściśle powiązany. Jest to również znak, że kod jest niekompletnie przetestowany lub może mieć kruche testy, które przyjmują błędne założenia dotyczące stanu programu.
W powyższym przykładzie, w którym zamiast tego używamy wstrzykiwania zależności, moglibyśmy łatwo zmienić program rejestrujący, jeśli byłoby to konieczne. Na przykład możemy użyć implementacji Loggera, która loguje się w innej lokalizacji lub używa innego formatu rejestrowania lub loguje się do bazy danych zamiast do pliku.
Zastrzyk setera
Zależności mogą być również wprowadzane przez seterów.
interface Logger {
public function log($message);
}
class Component {
private $logger;
private $databaseConnection;
public function __construct(DatabaseConnection $databaseConnection) {
$this->databaseConnection = $databaseConnection;
}
public function setLogger(Logger $logger) {
$this->logger = $logger;
}
public function core() {
$this->logSave();
return $this->databaseConnection->save($this);
}
public function logSave() {
if ($this->logger) {
$this->logger->log('saving');
}
}
}
Jest to szczególnie interesujące, gdy podstawowa funkcjonalność klasy nie polega na zależności od pracy.
Tutaj jedyną potrzebną zależnością jest DatabaseConnection
więc jest w konstruktorze. Zależność programu Logger
jest opcjonalna i dlatego nie musi być częścią konstruktora, dzięki czemu korzystanie z klasy jest łatwiejsze.
Pamiętaj, że w przypadku zastrzyku z ustawiaczem lepiej jest rozszerzyć funkcjonalność niż ją zastępować. Podczas ustawiania zależności nic nie potwierdza, że zależność nie zmieni się w pewnym momencie, co może prowadzić do nieoczekiwanych rezultatów. Na przykład FileLogger
można ustawić FileLogger
, a następnie MailLogger
. Powoduje to przerwanie enkapsulacji i utrudnia znalezienie dzienników, ponieważ zastępujemy zależność.
Aby temu zapobiec, powinniśmy dodać zależność od iniekcji ustawiającej, tak jak poniżej:
interface Logger {
public function log($message);
}
class Component {
private $loggers = array();
private $databaseConnection;
public function __construct(DatabaseConnection $databaseConnection) {
$this->databaseConnection = $databaseConnection;
}
public function addLogger(Logger $logger) {
$this->loggers[] = $logger;
}
public function core() {
$this->logSave();
return $this->databaseConnection->save($this);
}
public function logSave() {
foreach ($this->loggers as $logger) {
$logger->log('saving');
}
}
}
W ten sposób, ilekroć użyjemy podstawowej funkcjonalności, nie ulegnie ona uszkodzeniu, nawet jeśli nie zostanie dodana zależność rejestratora, a każdy dodany rejestrator zostanie użyty, nawet jeśli można dodać inny rejestrator. Rozszerzamy funkcjonalność zamiast ją zastępować .
Wstrzyknięcie pojemnika
Dependency Injection (DI) w kontekście korzystania z Dependency Injection Container (DIC) można postrzegać jako nadzbiór wstrzykiwania konstruktora. DIC zazwyczaj analizuje podpowiedzi konstruktora klasy i rozwiązuje jego potrzeby, skutecznie wstrzykując zależności potrzebne do wykonania instancji.
Dokładna implementacja wykracza daleko poza zakres tego dokumentu, ale w samym sercu DIC polega na użyciu podpisu klasy ...
namespace Documentation;
class Example
{
private $meaning;
public function __construct(Meaning $meaning)
{
$this->meaning = $meaning;
}
}
... aby automatycznie utworzyć jego instancję, polegając w większości na systemie automatycznego ładowania .
// older PHP versions
$container->make('Documentation\Example');
// since PHP 5.5
$container->make(\Documentation\Example::class);
Jeśli używasz PHP w wersji co najmniej 5.5 i chcesz uzyskać nazwę klasy w sposób pokazany powyżej, poprawnym sposobem jest drugie podejście. W ten sposób możesz szybko znaleźć zastosowania klasy za pomocą nowoczesnych IDE, co znacznie pomoże w potencjalnym refaktoryzacji. Nie chcesz polegać na zwykłych ciągach.
W tym przypadku Documentation\Example
wie, że potrzebuje Meaning
, a DIC z kolei utworzy typ Meaning
. Konkretna implementacja nie musi zależeć od instancji konsumującej.
Zamiast tego ustawiamy reguły w kontenerze przed utworzeniem obiektu, które instruują, w jaki sposób należy tworzyć instancje określonych typów, jeśli zajdzie taka potrzeba.
Ma to wiele zalet, jak w przypadku DIC
- Udostępnij typowe wystąpienia
- Zapewnij fabrykę do rozwiązania podpisu typu
- Rozwiąż podpis interfejsu
Jeśli zdefiniujemy zasady dotyczące sposobu zarządzania określonym typem, możemy uzyskać dokładną kontrolę nad tym, które typy są współużytkowane, tworzone lub tworzone z fabryki.