setter 注入

依赖关系也可以由 setter 注入。

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');
        }
    }
}

当类的核心功能不依赖于依赖工作时,这尤其有趣。

在这里,唯一需要的依赖是 DatabaseConnection 所以它在构造函数中。Logger 依赖项是可选的,因此不需要是构造函数的一部分,使类更容易使用。

请注意,使用 setter 注入时,最好扩展功能而不是替换它。设置依赖项时,没有任何事情可以确认依赖项在某些时候不会发生变化,这可能会导致意外结果。例如,首先可以设置 FileLogger,然后可以设置 MailLogger。这打破了封装并使得日志很难找到,因为我们正在替换依赖。

为了防止这种情况,我们应该使用 setter 注入添加依赖项,如下所示:

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');
        }
    }
}

像这样,每当我们使用核心功能时,即使没有添加记录器依赖项也不会中断,即使可以添加另一个记录器,也会使用添加的任何记录器。我们正在扩展功能而不是替换它。