简析 IoC 控制反转
设计模式原则
开闭原则:开放扩展,关闭修改。主要将的就是抽象化
里氏代换替换原则:任何基类可以出现的地方,子类一定可以出现。
依赖倒转原则:尽量依赖抽象接口编程而不要去依赖具体实例。????
接口隔离原则:使用多个隔离的接口要好过单个接口。
迪米特原则:一个实例尽量少知道与其他实例之间的相互作用关系,使功能模块相对独立。
合成复用原则:尽量使用聚合和组合,少用依赖。????
什么是 IoC
控制反转(简称 IoC), 是一种面向对象的设计思想,用来降低代码间的耦合度。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也就是将依赖被注入到要调用的对象中。控制反转一般和容器思想结合使用,Ioc 就是将对象交给容器去控制,而不是在对象内部直接控制,主要是控制对象的内部依赖。
谁控制谁
IoC 通过一个专门的容器来创建对象,通过 IoC 容器来控制对象
控制什么
控制实例的外部依赖
关于反转
正转:由对象去主动获取自己需要的依赖,并控制依赖叫正转
反转:由 new 对象的容器去获取并创建依赖,对象被动接受依赖,从而组合成一个具有具体功能的实例。
IoC 的作用
IoC 是一种面向对象的变成思想和指导准则。解决传统控制正转开发中在类内部主动创建依赖类,从而导致类的内部耦合,难以复用。IoC 把查找创建依赖的控制权反转给容器,由容器进行注入来组合创建一个对象,不同需求容器可以注入不同的对象,从而消除对象和依赖的耦合。
IoC 思想的实现
DI 依赖注入
依赖注入指容器在运行中动态的将依赖注入到组件中,从而使组件具有具体的功能。通过依赖注入可以在特殊的地方指定同一个抽象方法去处理不同依赖对象的逻辑,而不需要了解和修改具体抽象方法的实现,只需要关注自己的业务层。
谁依赖谁
实例化类依赖控制容器
为什么要依赖
实例化类需要依赖其他资源才可以处理具体业务
谁注入谁
IoC 容器向实例化类中注入了某个依赖
注入了什么
实例化类需要处理的依赖对象
如何实现
基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
基于 set 方法。实现特定属性的 public set 方法,来让外部容器调用传入所依赖类型的对象。
interface Way
{
public function go();
}
class GoShanghai
{
private $charger;
public function __construct(Way $charger)
{
$this->charger = $charger;
}
public function setWay(Way $way)
{
$this->way = $way;
}
public function go()
{
$this->charger->go();
}
}
class Car implements Way
{
public function go()
{
// TODO: Implement go() method.
print_r(“ 我通过开车去上海 ”);
}
}
// 基于构造函数和接口
$goshanghai = new GoShanghai(new Car());
$goshanghai->go();
// 基于 set 方法 | 展示需要因为构造函数运行会出错
$goshanghai = new GoShanghai();
$goshanghai->setWay(new Car());
$goshanghai->go();
依赖查找
依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key 等信息来确定获取对象的状态。
实现 IoC 的设计模式
OP 观察者模式
观察者模式主要用于处理对象直接一对多多关系。当外部资源发生改变,观察者会得到通知。观察者和被观察者直接是抽象耦合的,也就不影响实例的解藕。
什么是观察者
观察者是一对多关系中的多,观察者需要根据目标对象改变而改变的对象。
观察什么
观察目标对象的某些特定的状态。
如何观察
观察者继承自一个抽象观察类,抽象观察类实现注册、监听和通知功能。
如何实现
/**
* 观察者的抽象类
*/
abstract class Observer
{
// 这是一个目标类
protected $subject;
// 定义一个用于更新的抽象方法
public abstract function update();
}
/**
* 目标类
*/
class Subject {
// 观察者数组集合
private $observerList = array();
// 状态,这里遵循开闭原则将属性私有化
private $state = 0;
/**
* 获取状态的值
*/
public function getState() :int
{
return $this->state;
}
/**
* 设置状态的值
* @param int $state
*/
public function setState(int $state)
{
$this->state = $state;
$this->notifyAllObservers();
}
/**
* 注册观察者
* @param Observer $observer
*/
public function attach(Observer $observer)
{
array_push($this->observerList, $observer);
}
/**
* 通知观察者
*/
public function notifyAllObservers()
{
foreach ($this->observerList as $observer) {
$observer->update();
}
}
}
/**
* 观察者 A
*/
class AObserver extends Observer
{
public function __construct(Subject $subject)
{
// 注入目标依赖实现 IoC
$this->subject = $subject;
// 注册观察者
$this->subject->attach($this);
}
public function update()
{
// TODO: Implement update() method.
print_r(“A 观察者更新了支付业务的状态:{$this->subject->getState()}\n”);
}
}
// 创建一个目标对象
$subject = new Subject();
// 为目标对象绑定观察者
new AObserver($subject);
print_r(“ 目标对象的状态:{$subject->getState()}\n”);
$subject->setState(10);
// 目标对象的状态:0
// A 观察者更新了支付业务的状态:10
TP 模版模式
通过一个抽象基类定义执行它的模版也可以是方法,子类重写抽象方法的实现,但调用实在抽象基类中完成的,从而实现控制反转。就是基类控制行为,子类完成实现。
如何实现
/**
* 抽象模版类
*/
abstract class Template
{
// 抽象方法 A
public abstract function stepA();
// 抽象方法 B
public abstract function stepB();
// 执行定义的步骤, 这里使用了 final 关键字修饰,final 的作用是使这个方法不可被继承, 这样就不会被子类执行
public final function run()
{
$this->stepA();
$this->stepB();
}
}
/**
* 方案 A 的实例
*/
class FuncA extends Template
{
public function stepA()
{
// TODO: Implement stepA() method.
print_r(“ 方案 A 第一步 \n”);
}
public function stepB()
{
// TODO: Implement stepB() method.
print_r(“ 方案 A 第二步 \n”);
}
}
$funca = new FuncA();
$funca->run();
Laravel 中的 IoC
上面的例子可以看到控制反转使每个业务实例相对对立,他们的组装在代码执行时完成,当业务简单、依赖单一时看上去没有问题,但是当依赖复杂时会导致组装变得繁琐且难以梳理。在 laravel 中是通过容器 Container 来解决这个问题。下面我们通过几句话来简单看一下 laravel 中容器的使用。
启动 laravel 时就是启动了一个容器
容器内的实例在一次应用程序级调用中是单例的
通过 make 实现自动序列化依赖对象,代替 new
通过 bind 方法让每一个抽象接口和它的实例类一一对应
通过 resolveing 方法注册一个回调 callback 在绑定的对象解析完之后调用