DI&IoC
首先名词解释,DI全称是Dependency injection,依赖注入的意思。而IoC是Inversion of control 控制反转。要了解依赖注入和控制反转,首先我们不得不提到面向对象设计中的五大设计原则:S.O.L.I.D。
S.O.L.I.D - 面向对象五大设计原则SRP
- The Single Responsibility Principle 单一责任原则OCP
- The Open Closed Principle 开放封闭原则LSP
- The Liskov Substitution Principle 里氏替换原则ISP
- The Interface Segregation Principle 接口分离原则DIP
- The Dependency Inversion Principle 依赖倒置原则
这五种思想原则对我们平常的软件开发设计非常重要,大家可以具体去了解下。
依赖倒置原则
这里我们重点讲下依赖倒置原则:实体必须依靠抽象而不是具体实现。它表示高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。 在传统软件设计中,我们一般都是上层代码依赖下层代码,当下层代码变动时,我们上层代码要跟着变动,维护成本比较高。这时我们可以上层定义接口,下层来实现这个接口,从而使得下层依赖于上层,降低耦合度。 (PC主板和鼠标键盘接口就是一个很好的例子,各数据厂商根据主板上的接口来生产自己的鼠标键盘产品,这样鼠标坏了后我们可以随便换个符合接口要求的鼠标,而不用修改主板上的什么东西)
控制反转
上面讲的依赖倒置是一种原则,而控制反转就是实现依赖倒置的一种具体方法。 控制反转核心是把上层(类)所依赖单元的实例化过程交由第三方实现,而类中不允许存在对所依赖单元的实例化语句。 举个例子:
Comment{
public function afterInsert() {
$notification = new EmailNotification(...);
$notification->send(...);
}
}
如上,假如我们在用户提交评论后通知被评论者,这里通知方式是邮件,而且是直接在类中实例化邮件通知类,这样代码耦合度高,如果换个短信通知方式就不得不改这里面代码,具体好的实现我们下面会讲到。
依赖注入
依赖注入是一种设计模式,是一种IoC的具体实现,实现了IoC自然就符合依赖倒置原则。 依赖注入的核心思想是把类中所依赖单元的实例化过程放到类外面中去实现,然后把依赖注入进来。 常用的依赖注入方式有***属性注入***和***构造函数***注入。 比如用构造函数注入解耦上面代码:
// 通知接口interface
Notifaction{
public function send(...);
}
// 短信通知实现通知接口
class SmsNotification implements Notification
{ public function send(...)
{ ... }
}
// 评论类class
Comment{
... protected $notification;
public function __construct(Notification $smsNotification){
$this->notification = $smsNotification;
}
public function afterInsert(){
$this->notification->send(...);
}
}
// 实例化短信通知类
$smsNotification = new SmsNotification(...);
// 通过构造函数方法注入
$comment = new Comment($smsNotification);
...$comment->save();
这样,我们先定义Notification接口,里面有个send方法,让后面的通知者不管是邮件类还是短信类都实现这个接口,然后在外面通过构造函数方式注入进来,这样就解决了Comment类对具体通知方法的依赖,只要是实现了Notification接口的,都可以通过构造函数传进来,Comment类完全不用做任何修改。这样无论对于代码维护还是单元测试(可以模拟实现一个Notification类),都非常方便。
依赖注入是IoC的一种具体实现,是一种解耦手段。
IoC container/DI container
当项目比较大时,就会有许多类似上面评论类和通知类这种依赖关系,整个项目会非常复杂。这时候就需要一个集中的地方来管理这些依赖,我们把它叫IoC container 控制反转容器,它提供了动态地创建、注入依赖单元、映射依赖关系等功能。这样可以集中管理依赖关系,也减少很多代码量。