1. 依赖?
名词定义之类的就不罗嗦了,直接开搞!代码中经常遇到Class C里需要用到Class B,于是在C里new B(),可能B里需要用到Class A,于是在B里new A(),举一个常见的Mysql储存例子如下:
class Mysql{
public function save(String $data){
echo('save ['.$data.'] by Mysql'.PHP_EOL);
}
}
class Storage{
public function save(String $data){
$Mysql = new Mysql();
$Mysql->save($data);
}
}
$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by mysql
1.1 换了数据库
如果你只是写一个临时小脚本,这么写是没有问题的,但如果这是一个正经的项目,必然伴随着后续的升级迭代拓展优化,比如我们不用Mysql了,改用PostgreSQL,代码要新增PostgreSQL类,并修改Storage类如下:
class PostgreSQL{
public function save(String $data){
echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
}
}
class Storage{
public function save(String $data){
$PostgreSQL = new PostgreSQL();
$PostgreSQL->save($data);
}
}
$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by PostgreSQL
1.2 加了缓存层
比如流量变大了,增加了缓存层来加速查找,代码要新增Redis类,并修改Storage类如下:
class PostgreSQL{
public function save(String $data){
echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
}
}
class RedisTest{
public function save(String $data){
echo('set cache ['.$data.'] by Redis'.PHP_EOL);
}
}
class Storage{
public function save(String $data){
$PostgreSQL = new PostgreSQL();
$PostgreSQL->save($data);
$Redis = new $RedisTest;
$Redis->save($data);
}
}
$obj = new Storage();
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
如果再有后续的功能,比如要把PostgreSQL换成Oracle,那就要新增Oracle类,并修改Storage;比如要把Redis换成MongoDB,同理操作,非常麻烦,其实主逻辑并不关心到底使用了什么数据库,什么缓存,只要能存下来就行了,而且我的修改应该是不改动主函数的代码的,不然相应的单元测试是不是也要改?这违背了设计模式的开闭原则,非常麻烦。
2. 控制反转
控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup) ---维基百科
2.1 依赖注入
依赖注入的方式百科里有,这里不一一介绍,只举一个最常见的例子
class Storage{
public $db;
public $cache;
public function __construct(PostgreSQL $db, RedisTest $cache)
{
$this->db = $db;
$this->cache = $cache;
}
public function save(String $data){
$this->db->save($data);
$this->cache->save($data);
}
}
$obj = new Storage(new PostgreSQL(), new RedisTest());
$obj->save('Tom is smart');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
这时候我们替换db时候已经不需要修改Storage类了,但如果替换db,代码仍然有修改,比如new PostgreSQL()改为 new Oracle()之类的,故而我们试一试将需要通过 new 实例化构造函数,或者通过工厂模式实例化的任务交给容器。
2.2 Ioc容器 依赖注入
// 声明一个'DB'接口
interface DB
{
public function save(String $data);
}
class PostgreSQL implements DB{
public function save(String $data){
echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
}
}
// 声明一个'Cache'接口
interface Cache
{
public function save(String $data);
}
class RedisTest implements Cache{
public function save(String $data){
echo('set cache ['.$data.'] by Redis'.PHP_EOL);
}
}
class Ioc{
public $instances = [];
public function register($alias, $class)
{
$this->instances[$alias] = $class;
}
public function make($alias)
{
return $this->instances[$alias] ?? false;
}
}
class Storage{
public $db;
public $cache;
public function __construct(DB $db, Cache $cache)
{
$this->db = $db;
$this->cache = $cache;
}
public function save(String $data){
$this->db->save($data);
$this->cache->save($data);
}
}
$Ioc = new Ioc();
$Ioc->register('db', new PostgreSQL());
$Ioc->register('cache', new RedisTest());
$obj = new Storage($Ioc->make('db'), $Ioc->make('cache'));
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis
给类起了别名,主函数只负责调用,不用去关心细节具体是Mysql还是PostgreSQL,只要实现了DB接口就行。
2.3 Ioc容器 依赖查找
使用显式的依赖注入,要显式的往容器中注入实例化后的类,而使用PHP的反射机制,则可以实现自动注入。
// 声明一个'DB'接口
interface DB
{
public function save(String $data);
}
class PostgreSQL implements DB{
public function __construct(){}
public function save(String $data){
echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
}
}
// 声明一个'Cache'接口
interface Cache
{
public function save(String $data);
}
class RedisTest implements Cache{
public function __construct(){}
public function save(String $data){
echo('set cache ['.$data.'] by Redis'.PHP_EOL);
}
}
class Ioc{
public $instances = [];
public function getInstance($abstract){
//获取类的反射信息,也就是类的所有信息
$reflector = new \ReflectionClass($abstract);
//获取反射类的构造函数信息
$constructor = $reflector->getConstructor();
//获取反射类的构造函数的参数
$dependencies = $constructor->getParameters();
if(!$dependencies){
return new $abstract();
}
//一个类可能有多个依赖类
foreach ($dependencies as $dependency){
if(!is_null($dependency->getClass())){
$p[] = $this->make($dependency->getClass()->name);
//这里$p[0]是c的实例化对象,$p[1]是D的实例化对象
}
}
//创建一个类的新实例,给出的参数将传递到类的构造函数
//构造出B的实例化对象,向上反推到A的实例化对象
return $reflector->newInstanceArgs($p);
}
public function make($abstract){
return $this->getInstance($abstract);
}
}
class Storage{
public $db;
public $cache;
public function __construct(PostgreSQL $db, RedisTest $cache)
{
$this->db = $db;
$this->cache = $cache;
}
public function save(String $data){
$this->db->save($data);
$this->cache->save($data);
}
}
$Ioc = new Ioc();
$obj = $Ioc->make('Storage');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis