温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

php依赖注入

发布时间:2020-07-25 10:29:52 来源:网络 阅读:1208 作者:china_lx1 栏目:开发技术

在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式,重点在于介绍这种年轻的设计模式的适用场景及优势。


首先我们来一个实例,上代码

<?php

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B();
		$b->test();
	}
}

class B
{
	public function test()
	{
		echo 'this is B!<br>';
		$c = new C();
		$c->test();
	}
}

class C
{
	public function test()
	{
		echo 'this is C!<br>';
	}
}

$obj = new A();

$obj->test();


结果是:

this is A!
this is B!
this is C!


从代码分析,A类依赖B类,B类依赖C类。这是我们最原始的实现思路.这种实现思路很明显会有问题

假如我们现在B类修改下,代码如下:

class B
{

	public $name;

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

	public function test()
	{
		echo 'this is B'.$this->name.'!<br>';
		$c = new C();
		$c->test();
	}
}


此时再看我们原来A类test方法直接调用明显会有问题,于是此时我们需要将原来代码:

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B();
		$b->test();
	}
}

修改成:

class A
{
	public function test()
	{
		echo 'this is A!<br>';
		$b = new B('(class B)');//构造的时候多加了一个参数
		$b->test();
	}
}

如果此时C类构造方法也变动了呢,B的方法里面也同样受影响,很明显可用性非常低


为了解耦,此时我们用到第二种思路:构造器注入,直接上代码:

<?php

class A
{
	public $obj;

	public function __construct(B $b)
	{
		$this->obj = $b;
	}

	public function test()
	{
		echo 'this is A!<br>';
		$this->obj->test();
	}
}

class B
{
	public $obj;
	public function __construct(C $c)
	{
		$this->obj = $c;
	}

	public function test()
	{
		echo 'this is B!<br>';
		$this->obj->test();
	}
}

class C
{
	public function test()
	{
		echo 'this is C!<br>';
	}
}
$c = new C();
$b = new B($c);
$obj = new A($b);
$obj->test();

这种方法可以解决第一种方法,如果依赖的类构造方法(比如B类)有所改动,A类不需要改动任何代码。但是久而久之,这种方法毛病也出来了,我们首先必须要先实例化C类,再实例化B类,最后再实例化A类。


了解到第二种方案需要优化,于是出现了基本成型的第三种方案,此时我们用到了container(容器)和instance(实例),直接上代码:

class Container
{

	public static $_definations = [];
	
	
	public function get($class)
	{

		//当前类所依赖的类
		$depends = [];
		
		$tc = new ReflectionClass($class);
		//得到构造方法
		$constructor = $tc->getConstructor();

		//得到构造方法的参数
		if($constructor !== NULL)
		{
			foreach($constructor->getParameters() as $parameter)
			{
				if($parameter->isDefaultValueAvailable())
				{
					$depends[] = $parameter->getDefaultValue();
				}
				else
				{
					$pc = $parameter->getClass();
					$instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName());
					$depends[] = $instance;
				}
			}
		}

		foreach($depends as $k => $v)
		{
			if($v instanceof Instance)
			{
				if($v->id !== NULL)
				{
					$depends[$k] = $this->get($v->id);
				}
			}
		}

		$tm_instance = $tc->newInstanceArgs($depends);
		return $tm_instance;
	}
}

class Instance{
	/**
	 * @var 类唯一标示
	 */
	public $id;

	/**
	 * 构造函数
	 * @param string $id 类唯一ID
	 * @return void
	 */
	public function __construct($id)
	{
		$this->id = $id;
	}

	/**
	 * 获取类的实例
	 * @param string $id 类唯一ID
	 * @return Object Instance
	 */
	public static function getInstance($id)
	{
		return new self($id);
	}
}

class Base{
	
}

class A extends Base{
	private $instanceB;

	public function __construct(B $instanceB)
	{
		$this->instanceB = $instanceB;
	}

	public function test()
	{
		echo 'this is A!<br/>';
		$this->instanceB->test();
	}
}


class B  extends Base{
	private $instanceC;

	public function __construct(C $instanceC)
	{
		$this->instanceC = $instanceC;
	}

	public function test()
	{
		echo 'this is B!<br/>';
		return $this->instanceC->test();
	}
}

class C  extends Base{
	public function test()
	{
		echo 'this is C!';
	}
}


$container = new Container();

$obj_a = $container->get('A');

$obj_a->test();

此方法有参考yii2中yii2\di\container实现,只是将它简化了,更容易看懂。重点看看container的get方法


现在我们增加set方法,自定义函数,直接上代码精简版

class Container
{
	/**
	*@var array 存储各个类的定义  以类的名称为键
	*/
	public static $_definations = [];

	
	public function get($class, $params = [], $props = [])
	{
		if(!isset(self::$_definations[$class]))
			return $this->build($class, $params, $props);

		//如果已经被定义过的
		$_defination = self::$_definations[$class];
		
		if(is_callable($_defination, true))
		{
			return call_user_func($_defination, $this, $params, $props);
		}
		else if(is_object($_defination))
		{
			return $_defination;
		}
		else
		{
			throw new Exception($class . '声明错误');
		}
	}

	public function set($class, $_defination)
	{
		self::$_definations[$class] = $_defination;
		
	}

	public function build($class, $params, $props)
	{
		//当前类所依赖的类
		$depends = [];
		
		$tc = new ReflectionClass($class);
		//得到构造方法
		$constructor = $tc->getConstructor();

		//得到构造方法的参数
		if($constructor !== NULL)
		{
			foreach($constructor->getParameters() as $parameter)
			{
				if($parameter->isDefaultValueAvailable())
				{
					$depends[] = $parameter->getDefaultValue();
				}
				else
				{
					$pc = $parameter->getClass();
					$instance = Instance::getInstance($pc == NULL ? NULL : $pc->getName());
					$depends[] = $instance;
				}
			}
		}

		foreach($depends as $k => $v)
		{
			if($v instanceof Instance)
			{
				if($v->id !== NULL)
				{
					$depends[$k] = $this->get($v->id);
				}
			}
		}

		$tm_instance = $tc->newInstanceArgs($depends);
		return $tm_instance;
	}
}


增加了一个set方法,并将get方法稍微改版了一下,现在我们看看怎么调用set方法:

$container = new Container();

$container->set('foo', function($container, $params, $config){
	print_r($params);
	print_r($config);
});

$container->get('foo', ['p1' => 'pv1'], ['c1' => 'cv1']);


输出结果为:

Array ( [p1] => pv1 ) Array ( [c1] => cv1 )


再看看另外一种情况调用:

class Test
{
    public function mytest()
    {
        echo 'this is a test';
    }
}

$container->set('testObj', new Test());

$test = $container->get('testObj');
$test->mytest();


输出结果为:

this is a test


上面的代码只是作者粗略的写了下简单版本,很多地方不是太完善,这版本是为了理解yii2的container打下基础,稍后会出yii2的依赖注入源码分析

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI