依赖注入
说这个话题之前先讲一个比较高端的思想--'依赖倒置原则'
"依赖倒置是一种软件设计思想,在传统软件中,上层代码依赖于下层代码,当下层代码有所改动时,上层代码也要相应进行改动,因此维护成本较高。而依赖倒置原则的思想是,上层不应该依赖下层,应依赖接口。意为上层代码定义接口,下层代码实现该接口,从而使得下层依赖于上层接口,降低耦合度,提高系统弹性"
上面的解释有点虚,下面我们以实际代码来解释这个理论
比如有这么条需求,用户注册完成后要发送一封邮件,然后你有如下代码:
class Mail{
public function send()
{
/*这里是如何发送邮件的代码*/
}
}
然后又注册的类'Register.class.php'
class Register{
private $_emailObj;
public function doRegister()
{
/*这里是如何注册*/
$this->_emailObj = new Mail();
$this->_emailObj->send();//发送邮件
}
}
然后开始注册
include 'Mail.class.php';
include 'Register.class.php';
$reg = new Register();
$reg->doRegister();
看起来事情很简单,你很快把这个功能上线了,看起来相安无事... xxx天过后,产品人员说发送邮件的不好,要使用发送短信的,然后你说这简单我把'Mail'类改下...
又过了几天,产品人员说发送短信费用太高,还是改用邮件的好... 此时心中一万个草泥马奔腾而过..
以上场景的问题在于,你每次不得不对'Mail'类进行修改,而且还得改Register类。代码复用性很低,高层过度依赖于底层。那么我们就考虑'依赖倒置原则',让底层继承高层制定的接口,高层依赖于接口。
interface Mail
{
public function send();
}
class Email implements Mail()
{
public function send()
{
//发送Email
}
}
class SmsMail implements Mail()
{
public function send()
{
//发送短信
}
}
PHP接口(interface)的特点
1、接口的方法必须是公开的。
2、接口的方法默认是抽象的,所以不在方法名前面加abstract。
3、接口可以定义常量,但不能定义成员属性,常量的定义和用法和类中常量一样。
4、类可以实现多个接口(相当于把多个功能集于一身,如手机实现了小灵通、MP3、MP4的功能)
5、接口也可以继承接口。
class Register
{
private $_mailObj;
public function __construct(Mail $mailObj)
{
$this->_mailObj = $mailObj;
}
public function doRegister()
{
/*这里是如何注册*/
$this->_mailObj->send();//发送信息
}
}
下面开始发送信息
/* 此处省略若干行 */
$reg = new Register(); //实例化
$emailObj = new Email();
$smsObj = new SmsMail();
$reg->doRegister($emailObj);//使用email发送
$reg->doRegister($smsObj);//使用短信发送
/* 你甚至可以发完邮件再发短信 */
上面的代码解决了'Register'对信息发送类的依赖,使用构造函数注入的方法,使得它只依赖于发送短信的接口,只要实现其接口中的'send'方法,不管你怎么发送都可以。上例就使用了"注入"这个思想,就像注射器一样将一个类的实例注入到另一个类的实例中去,需要用什么就注入什么。当然"依赖倒置原则"也始终贯彻在里面。"注入"不仅可以通过构造函数注入,也可以通过属性注入,上面你可以可以通过一个"setter"来动态为"mailObj"这个属性赋值。
和工厂模式不同点
依赖注入的想法和工厂模式很像。它们是软件结构控制反转(IoC)的两种实现。简单地说,它们是针对同一问题的两种解决方案。
虽然两种模式都是将对象的创建从应用的逻辑中分离,但是依赖注入比工程模式更清晰。通过依赖注入,你的类就是 POJO,它只知道依赖而不关心它们怎么获取。使用工厂模式,你的类需要通过工厂来获取依赖。因此,使用 DI 会比使用工厂模式更容易测试。
使用工厂时,您的代码仍然负责创建对象。通过DI,您将该职责外包给另一个类或框架,该框架与您的代码分开。
下面就是工厂模式
<?php
class Factory {//创建一个基本的工厂类
static public function fac($id){//创建一个返回对象实例的静态方法
if(1 == $id){
return new A();
}elseif(2==$id){
return new B();
}elseif(3==$id){
return new C();
}else{
return new D();
}
}
}
interface FetchName {//创建一个接口 接口可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。接口是通过 interface 关键字来定义的,其中定义所有的方法都是空的。接口中定义的所有方法都必须是公有的。
public function getname();
}
//implements 实现接口类
class A implements FetchName{
private $name = "AAAAA";
public function getname(){ return $this->name; }
}
class B implements FetchName{
private $name = "BBBBB";
public function getname(){ return $this->name; }
}
class C implements FetchName{
private $name = "CCCCC";
public function getname(){ return $this->name; }
}
class D implements FetchName{
private $name = "DDDDD";
public function getname(){ return $this->name; }
}
$o = Factory::fac(6);//调用工厂类中的方法
if($o instanceof FetchName){ //instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例
echo $o->getname();//DDDDD
}
$p=Factory::fac(2);
echo $p->getname();//BBBBB