依赖注入容器(DIC)

依赖注入容器(DIC)可以看作是注册表。每个依赖项都有一个名称,可以是类名或标识该依赖项的唯一名称。

另外,可以为每个依赖项配置依赖项所需的参数。上面依赖项Smtp的参数有主机,端口,用户名和密码。

将所有依赖项注册到一个容器中之后,该容器负责使用get()方法返回这些依赖项的实例。

一般依赖注入容器存储的是依赖项类名称的引用。只有当需要依赖项时,才会去动态进行实例化。如果存储的是依赖项类的实例。会导致性能问题。

示例-简单的依赖注入容器

让我们看一下简单的依赖注入容器的示例代码:
创建DiContainer的实例并注册一个依赖项。

$DiContainer->register('db', 'PDO')
    ->addArgument('mysql:host=localhost;dbname=db')
    ->addArgument('username')
    ->addArgument('password');

register方法注册一个依赖项,并接受两个参数。第一个参数是已赋予依赖项的唯一名称。也就是查找依赖项时使用的名称。第二个参数是依赖项的类名。
addArgument()方法用于添加PDO类构造函数所需的参数。

在注册了依赖项之后,现在可以通过三种不同的方式对其进行访问

$db = $DiContainer->getInstance('db');

$db = $DiContainer->getSingleInstance('db');

$db = $DiContainer->db;
  • 使用依赖项名称调用getInstance()方法,将返回依赖项的新实例。
  • 调用getSingleInstance()方法将仅返回一次实例。DiContainer将在内部维护此实例。
  • 最后一种方法允许您将依赖项名称用作属性。

在某些情况下,可能希望使用已注册的依赖项作为正在注册的另一个依赖项的参数。下面的代码示例显示了如何解决此问题。

class Mailer {
    private $transport;
    public function __construct($transport){
        $this->transport = $transport;
    }
}

class Smtp{
    public function __construct($host, $port, $user, $pass){
        // Process arguments
    }
}

$DiContainer = new DiContainer();

$DiContainer->register('smtp', 'Smtp')
    ->addArgument('host')
    ->addArgument('port')
    ->addArgument('user')
    ->addArgument('pass');
    
$DiContainer->register('mailer', 'Mailer')
    ->addArgument('@@smtp');
    
$mailer = $DiContainer->getInstance('mailer');

依赖项smtp已被注册并命名为smtp。Mailer类需要将smtp实例传递给其构造函数。注册Mailer依赖项时,双@@后跟依赖项名称用于引用先前注册的依赖项。

DIC的实现方式不同,每种编程语言都有自己的语法。一些DIC不仅将参数传递给类构造函数,甚至可以使用参数调用类方法。
PHP的简单反射功能使任何人都可以轻松开发自己的DIC库。

DiContainer类

<?php

	class DiContainer {
		
		protected $services = array();
		protected $instances = array();
		
		public function register($serviceName, $className){
			$service = new Service($className, $this);
			$this->services[$serviceName] = $service;
			return $service;
		}
		
		public function getInstance($serviceName){
			
			if(!isset($this->services[$serviceName])){
				throw new RuntimeException(sprintf("Failed to get service '%s'. Service is not registered", $serviceName));
			}
			
			$service = $this->services[$serviceName];

			$refClass = new ReflectionClass($service->getClassName());
			return $refClass->newInstanceArgs($service->getArguments());
		}
		
		public function getSingleInstance($serviceName){
			if(isset($this->instances[$serviceName])){
				return $this->instances[$serviceName];
			}
			
			$instance = $this->getInstance($serviceName);
			$this->instances[$serviceName] = $instance;
			
			return $instance;
		}
		
		public function __get($serviceName){
			return $this->getInstance($serviceName);
		}
	}
	
	class Service{
		protected $className;
		protected $diContainer;
		protected $serviceArgs = array();
		
		public function __construct($className, $diContainer){
			$this->className = $className;
			$this->diContainer = $diContainer;
		}
		
		public function getClassName(){
			return $this->className;
		}
		
		public function addArgument($argument){
			if(is_string($argument)){
				if (substr($argument,0,2)=='@@'){
					$serviceName = substr($argument,2);
					$argument = $this->diContainer->getInstance($serviceName);
				}
			}
			$this->serviceArgs[] = $argument;
			return $this;
		}
		
		public function getArguments(){
			return $this->serviceArgs;
		}
	}

?>
PHP实现依赖项注入模式容器示例

依赖项注入模式在许多应用程序中得到了广泛的应用。

什么是依赖项注入模式

依赖项注入模式实际上是一个非常简单的模式。
让我们看一下下面的代码

class Mailer {
    private $transport;
    private $emailTemplate;
    
    public function __construct(){
        $this->transport = new Smtp('host', 'port', 'user', 'pass');
        $this->emailTemplate = file_get_contents('template.html');
    }
    
    public function send($from, $to, $subject){
        $this->transport->send($from, $to, $subject, $this->emailTemplate);
    }
}

$mailer = new Mailer();
$mailer->send('theitroad', 'test@theitroad.com', '测试邮件');

我们定义了一个简单的Mailer类,这个类加载HTML模板后将其发送到指定的邮箱。

我们使用了smtp来发送邮件。但是如果这是要求使用第三方API(比如qq邮箱)发送邮件。那么这个类不符合要求。
我们需要把smtp代码替换成QQ邮箱的api。这种紧密耦合的方式不灵活。

解决的方法是,不要对Smtp类进行硬编码,而是将其作为配置数据提供给Mailer类。
先创建Smtp类的实例,然后通过Mailers的构造函数或者setter方法将其注入Mailer类中,可以完成此操作。
同时,如果没有 $transport,Mailer类就无法运行。
所以就叫做 依赖项注入模式

class Mailer {
    private $transport;
    
    private $emailTemplate;
    
    public function __construct($transport){
        $this->transport = $transport;
        $this->emailTemplate = file_get_contents('template.html');
    }
    
    public function send($from, $to, $subject){
        $this->transport->send($from, $to, $subject, $this->emailTemplate);
    }
}

class Smtp {
    public function send($from, $to, $subject, $message){
        // Send the message using smtp.
    }
}

$mailer = new Mailer(new Smtp());
$mailer->send('Syed Hussain', 'john@domain.com', 'Marketing Email');

所以依赖注入是向依赖于其他服务(Smpt类)的客户端(Mailer类)提供配置数据的能力。

您可能已经在某个时候有意或无意地使用了依赖项注入模式。依赖项注入也称为控制反转(IoC)。这个名称来自于这样一个事实:来自客户端的控制被倒置了,这意味着客户端不再对它的依赖项负责,而控制被交给了另一个实体。

如果从客户端删除依赖项,则意味着必须在另一个范围中创建依赖项。这里使用的术语范围是“其他地方”,这些依赖项可能有它们自己的依赖项。在任何情况下,所有依赖项必须存在,依赖项(客户端)才能使用它们。这样做的副作用是代码膨胀,并引入错误。例如,您可能知道某个类需要哪些依赖项,但是您的同事可能不知道,而且可能使用不同的依赖项,这可能会导致错误。这就是主要使用依赖注入容器(DIC)的地方。DIC是一种实现,可以用任何编程语言实现。

日期:2019-04-16 23:59:04 来源:oir作者:oir