控制反转和依赖注入

先放个资料  

知乎链接

 Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好Ioc呢?理解好Ioc的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

  为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

 

1:IOC容器 控制反转

    IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

2:DI 依赖注入

      DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现

 

 

3:依赖注入实现

1:示例

class DbMysql
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

class DbRedis
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class controller
{
public $mysql;
public $redis;

public function __construct()
{
$this->mysql = new DbMysql('host', 'name', 'pwd');
$this->redis = new DbRedis('host', 'name', 'pwd');
}

public function action()
{
$this->mysql->query();
$this->redis->set();
}
}

$c = new Controller();
$c->action();
/**
* 输出:
* DbMysql::query
* DbRedis::set
*/
普通的实现方式,耦合度高。

 

2:示例

class DbMysql
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

class DbRedis
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class controller
{
public $mysql;
public $redis;

public function __construct($mysql, $redis)
{
$this->mysql = $mysql;
$this->redis = $redis;
}

public function action()
{
$this->mysql->query();
$this->redis->set();
}
}

$mysql = new DbMysql('host', 'name', 'pwd');
$redis = new DbRedis('host', 'name', 'pwd');
$c = new Controller($mysql, $redis);
$c->action();
/**
* 输出:
* DbMysql::query
* DbRedis::set
*/
实现了依赖注入和控制反转,但是没有使用容器类。

 

 3:示例

示例三
class DbMysql
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

class DbRedis
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class controller
{
public $mysql;
public $redis;

public function __construct($mysql, $redis)
{
$this->mysql = $mysql;
$this->redis = $redis;
}

public function action()
{
$this->mysql->query();
$this->redis->set();
}
}

class Container
{

public $bindings = [];

public function bind($key, Closure $value)
{
$this->bindings[$key] = $value;
}

public function make($key)
{
$new = $this->bindings[$key];
return $new();
}

}

$app = new Container();
$app->bind('mysql', function () {
return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', function () use ($app) {
return new Controller($app->make('mysql'), $app->make('redis'));
});
$controller = $app->make('controller');
$controller->action();
/**
* 输出:
* DbMysql::query
* DbRedis::set
*/
实现了基本的容器类,容器类中有两个方法,bind和make,一个是绑定操作,一个是实例化操作。将每一个需要使用到的类使用关键字绑定到容器类中去,但是每一个类仍然需要手动去实例化,这里引入了闭包函数,主要作用是在调用的时候才真正去实例化,而如果仅仅是绑定了一个类,是不会实例化这个类的。

 

 4:示例

示例四
class T
{
public $t;
}

class X
{
public $x;

private function __construct()
{
}
}

class Y
{
public $x;

public function __construct()
{
}
}

interface Is
{
}

class Sis implements Is
{

}

class S
{
public $s;

public function __construct(string $s, int $i, array $a, Is $object)
{
$this->s = $s;
}
}

function reflectionClass($className, array $inParams = [])
{
$reflection = new ReflectionClass($className);
// isInstantiable() 方法判断类是否可以实例化
$isInstantiable = $reflection->isInstantiable();
if ($isInstantiable) {
// getConstructor() 方法获取类的构造函数,为NULL没有构造函数
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// 没有构造函数直接实例化对象返回
return new $className;
} else {
// 有构造函数
$params = $constructor->getParameters();
if (empty($params)) {
// 构造函数没有参数,直接实例化对象返回
return new $className;
} else {
// 构造函数有参数,将$inParams传入实例化对象返回
return $reflection->newInstanceArgs($inParams);
}
}
}
return null;
}

$t = reflectionClass('T');
var_dump($t instanceof T);
$x = reflectionClass('X');
var_dump($x instanceof X);
$x = reflectionClass('Y');
var_dump($x instanceof Y);
$s = reflectionClass('S', ['asdf', 123, [1, 2], (new Sis)]);
var_dump($s instanceof S);
/**
* 输出:
* bool(true)
* bool(false)
* bool(true)
* bool(true)
*/
引入反射类,他的作用是可以实例化一个类,和new操作一样。但是实例化一个类所需要的参数,他都能自动检测出来。并且能够检测出来这个参数是不是一个继承了接口的类。上面说一个类依赖另一个类,然后将另一个类作为参数注入,这个反射能够检测出一个类实例化的时候需要什么样的类,好像有点眉目了是吧。

 

 5:示例

示例五
class DbMysql
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

class DbRedis
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class controller
{
public $mysql;
public $redis;

public function __construct($mysql, $redis)
{
var_dump($mysql);var_dump($redis);
$this->mysql = $mysql;
$this->redis = $redis;
}

public function action()
{
is_object($this->mysql) && $this->mysql->query();
is_object($this->redis) && $this->redis->set();
}
}

class Container
{

public $bindings = [];

public function bind($key, $value)
{
if (!$value instanceof Closure) {
$this->bindings[$key] = $this->getClosure($value);
}
else{
$this->bindings[$key] = $value;
}
}

public function getClosure($value)
{
return function () use ($value) {
return $this->build($value);
};
}

public function make($key)
{
if (isset($this->bindings[$key])) {
return $this->build($this->bindings[$key]);
}
return $this->build($key);
}

public function build($value)
{
if ($value instanceof Closure) {
return $value();
}
// 实例化反射类
$reflection = new ReflectionClass($value);
// isInstantiable() 方法判断类是否可以实例化
$isInstantiable = $reflection->isInstantiable();
if ($isInstantiable) {
// getConstructor() 方法获取类的构造函数,为NULL没有构造函数
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// 没有构造函数直接实例化对象返回
return new $value;
} else {
// 有构造函数
$params = $constructor->getParameters();
if (empty($params)) {
// 构造函数没有参数,直接实例化对象返回
return new $value;
} else {
$dependencies = [];
// 构造函数有参数
foreach ($params as $param) {
$dependency = $param->getClass();
if (is_null($dependency)) {
// 构造函数参数不为class,返回NULL
$dependencies[] = NULL;
} else {
// 类存在创建类实例
$dependencies[] = $this->make($param->getClass()->name);
}
}
return $reflection->newInstanceArgs($dependencies);
}
}
}
return null;
}

}

$app = new Container();
$app->bind('mysql', function () {
return new DbMysql('host', 'name', 'pwd');
});
$app->bind('redis', function () {
return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
* 输出:
* NULL
* NULL
*/
容器类中引入反射,容器类bind方法升级,不仅仅支持闭包绑定,而且支持类名绑定。示例三中的bind方法仅仅支持绑定一个关键字为闭包,而类的实例操作,需要写到闭包函数中去。现在有了反射,可以直接使用类名,反射会根据类名自动去实例化这个类。

但是这个例子中输出两个NULL,Controller类的两个参数均为NULL,反射类并没有自动去找到Controller依赖的DbMysql和DbRedis去实例化。这是为什么呢?现在就需要引入另一个东西,针对接口编程。

这个例子中我们知道Controller类的两个参数是类,但是我们定义的构造函数中并没有声明,现在这种定义方式的两个参数,是类,是字符串,是整型,完全没区别的,反射类无法检测出这两个参数是类,我们的反射方法里面如果检测到构造函数的参数不是类直接返回NULL,所以这里输出了两个NULL。

 

 6:示例

示例六
interface SMysql
{
public function query();
}

class DbMysql implements SMysql
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

interface SRedis
{
public function set();
}

class DbRedis implements SRedis
{
public function __construct($host, $name, $pwd)
{
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class controller
{
public $mysql;
public $redis;

public function __construct(SMysql $mysql, SRedis $redis)
{
$this->mysql = $mysql;
$this->redis = $redis;
}

public function action()
{
is_object($this->mysql) && $this->mysql->query();
is_object($this->redis) && $this->redis->set();
}
}

class Container
{

public $bindings = [];

public function bind($key, $value)
{
if (!$value instanceof Closure) {
$this->bindings[$key] = $this->getClosure($value);
} else {
$this->bindings[$key] = $value;
}
}

public function getClosure($value)
{
return function () use ($value) {
return $this->build($value);
};
}

public function make($key)
{
if (isset($this->bindings[$key])) {
return $this->build($this->bindings[$key]);
}
return $this->build($key);
}

public function build($value)
{
if ($value instanceof Closure) {
return $value();
}
// 实例化反射类
$reflection = new ReflectionClass($value);
// isInstantiable() 方法判断类是否可以实例化
$isInstantiable = $reflection->isInstantiable();
if ($isInstantiable) {
// getConstructor() 方法获取类的构造函数,为NULL没有构造函数
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// 没有构造函数直接实例化对象返回
return new $value;
} else {
// 有构造函数
$params = $constructor->getParameters();
if (empty($params)) {
// 构造函数没有参数,直接实例化对象返回
return new $value;
} else {
$dependencies = [];
// 构造函数有参数
foreach ($params as $param) {
$dependency = $param->getClass();
if (is_null($dependency)) {
// 构造函数参数不为class,返回NULL
$dependencies[] = NULL;
} else {
// 类存在创建类实例
$dependencies[] = $this->make($param->getClass()->name);
}
}
return $reflection->newInstanceArgs($dependencies);
}
}
}
return null;
}

}

$app = new Container();
$app->bind('SMysql', function () {
return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
return new DbRedis('host', 'name', 'pwd');
});
$app->bind('controller', 'controller');
$controller = $app->make('controller');
$controller->action();
/**
* 输出:
* DbMysql::query
* DbRedis::set
*/
使用接口了以后脚本执行正常。

首先容器绑定了接口SMysql和SRedis分别对应的闭包,绑定的关键字为接口名称,也就是说在容器中多次绑定一个接口只会绑定一个闭包一个类的实现,controller类的构造函数中声明了需要两个分别继承自SMysql接口和SRedis接口的类。容器中已经有了这两个接口的实现方式,所以直接调用闭包函数实例化类,然后将结果提供给controller类进行实例化。

本例中,我们仅仅make了controller类,也就是说我们只需要实例化controller类,而controller类依赖的DbMysql类和DbRedis类,IOC容器会自动帮我们实例化并注入。

本例中controller类的实例化很简单:

$app->bind('controller', 'controller');
$controller = $app->make('controller');
但是DbMysql和DbRedis就比较丑了:

$app->bind('SMysql', function () {
return new DbMysql('host', 'name', 'pwd');
});
$app->bind('SRedis', function () {
return new DbRedis('host', 'name', 'pwd');
});
其实是这两个类写的有问题,因为这两个类不是面对接口编程。但是这种自定义闭包函数的绑定非常方便,完全满足任何类的自由实例化。我们要做是依赖注入,那就全部使用接口来实现,看下一个例子。

 

 7:示例

示例七
interface MConfig
{
public function getConfig();
}

class MysqlConfig implements MConfig
{
public function getConfig()
{
// 获取配置
return ['host', 'name', 'pwd'];
}
}

interface RConfig
{
public function getConfig();
}

class RedisConfig implements RConfig
{
public function getConfig()
{
// 获取配置
return ['host', 'name', 'pwd'];
}
}

interface SMysql
{
public function query();
}

class DbMysql implements SMysql
{
public $config;

public function __construct(MConfig $config)
{
$this->config = $config->getConfig();
// do something
}

public function query()
{
echo __METHOD__ . PHP_EOL;
}
}

interface SRedis
{
public function Set();
}

class DbRedis implements SRedis
{
public function __construct(RConfig $config)
{
$this->config = $config->getConfig();
// do something
}

public function set()
{
echo __METHOD__ . PHP_EOL;
}
}

class Controller
{
public $mysql;
public $redis;

public function __construct(SMysql $mysql, SRedis $redis)
{
$this->mysql = $mysql;
$this->redis = $redis;
}

public function action()
{
is_object($this->mysql) && $this->mysql->query();
is_object($this->redis) && $this->redis->set();
}
}

class Container
{

public $bindings = [];

public function bind($key, $value)
{
if (!$value instanceof Closure) {
$this->bindings[$key] = $this->getClosure($value);
} else {
$this->bindings[$key] = $value;
}
}

public function getClosure($value)
{
return function () use ($value) {
return $this->build($value);
};
}

public function make($key)
{
if (isset($this->bindings[$key])) {
return $this->build($this->bindings[$key]);
}
return $this->build($key);
}

public function build($value)
{
if ($value instanceof Closure) {
return $value();
}
// 实例化反射类
$reflection = new ReflectionClass($value);
// isInstantiable() 方法判断类是否可以实例化
$isInstantiable = $reflection->isInstantiable();
if ($isInstantiable) {
// getConstructor() 方法获取类的构造函数,为NULL没有构造函数
$constructor = $reflection->getConstructor();
if (is_null($constructor)) {
// 没有构造函数直接实例化对象返回
return new $value;
} else {
// 有构造函数
$params = $constructor->getParameters();
if (empty($params)) {
// 构造函数没有参数,直接实例化对象返回
return new $value;
} else {
$dependencies = [];
// 构造函数有参数
foreach ($params as $param) {
$dependency = $param->getClass();
if (is_null($dependency)) {
// 构造函数参数不为class,返回NULL
$dependencies[] = NULL;
} else {
// 类存在创建类实例
$dependencies[] = $this->make($param->getClass()->name);
}
}
return $reflection->newInstanceArgs($dependencies);
}
}
}
return null;
}

}

$app = new Container();
$app->bind('MConfig', 'MysqlConfig');
$app->bind('RConfig', 'RedisConfig');
$app->bind('SMysql', 'DbMysql');
$app->bind('SRedis', 'DbRedis');
$app->bind('controller', 'Controller');
$controller = $app->make('controller');
$controller->action();
/**
* 输出:
* DbMysql::query
* DbRedis::set
*/
我们只需要实例化一个IOC容器,而后我们要使用到的所有类,它都能帮我们自动依赖注入。当然,这种实现是建立在组件针对接口编程上面的,否则就是示例六中的那种。

 

posted @ 2023-02-27 16:31  给香菜送点香菜  阅读(30)  评论(0)    收藏  举报