PHP控制反转(IOC)和依赖注入(DI)

一.控制反转,依赖注入简介

控制反转和依赖注入说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合
  1. 什么是控制反转

Inversion of Control 简写IOC ,他不是一种技术,从容器的角度在描述来看:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源

在这里需要理解几个词:谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了?

1.1谁控制谁,控制什么

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

1.2为何是反转,哪些方面反转了

有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转,考虑一下常规情况下的应用程序,如果要在A里面使用C,会怎么做呢?当然是直接去创建C的对象,也就是说,是在A类中主动去获取所需要的外部资源C($c = new C();)一句话来解释:正转就是按照普通的在类中直接创建对象实例,如 $c = new C(),这种情况被称为正的。那么什么是反转呢?就是A类不再主动去获取C,而是被动等待,等待IoC/DI的容器获取一个C的实例,然后反向的注入到A类中,为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了

1.3 控制反转能做些什么以及作用?

传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;但是有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

注:IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
  IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.4 控制反转分类

  • 依赖注入(Dependency Injection 简写DI) 这个应用比较广泛

  • 依赖查找(Dependency Lookup 检查DL)

2.什么是依赖注入

Dependency Injection 简写DI,他是实现控制反转思想的的技术方式,从应用程序的角度来描述:应用程序依赖容器创建并注入它所需要的外部资源

为什么使用依赖注入呢

有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。也需要理解几个词:谁依赖谁,为什么需要依赖,谁注入谁,注入了什么?

2.1谁依赖于谁

当然是应用程序依赖于IoC容器

2.2为什么需要依赖

应用程序需要IoC容器来提供对象需要的外部资源

2.3谁注入谁

很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

2.4 注入了什么

就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

二.案例演示

案例1

没有IoC/DI的时候,常规的A类使用C类的示例

<?php
/**
 * 没有IoC/DI的时候,常规的A类使用C类的示例
 */

/**
 * Class c
 */
class c
{
    public function say()
    {
        echo 'hello';
    }
}

/**
 * Class a
 */
class a
{
    private $c;
    public function __construct()
    {
        $this->c = new C(); // 实例化创建C类
    }

    public function sayC()
    {
        echo $this->c->say(); // 调用C类中的方法
    }
}

$a = new a();
$a->sayC(); 

当有了IoC/DI的容器后,a类依赖c实例注入的示例

<?php
/**
 * 当有了IoC/DI的容器后,a类依赖c实例注入的示例
 */

/**
 * Class c
 */
class c {

    public function say()
    {
        echo 'hello';
    }
}

/**
 * Class a
 */
class a {
    private $c;
    public function setC(C $c) {
        $this->c = $c; // 实例化创建C类
    }

    public function sayC(){
        echo $this->c->say(); // 调用C类中的方法
    }
}

$c = new C();

$a = new a();

$a->setC($c);

$a->sayC();
其实IoC/DI对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入它所需要的资源了

案例2

<?php
 
class  A
{
     public  $b ;
     public  $c ;
     public  function  A()
     {
         //TODO
     }
     public  function  Method()
     {
         $this ->b= new  B();
         $this ->c= new  C();
         
         $this ->b->Method();
         $this ->c->Method();
         
         //TODO
     }
}
 
class  B
{
     public  function  B()
     {
         //TODO
     }
     public  function  Method()
     {
         //TODO
         echo  'b' ;
     }
}
 
class  C
{
     public  function  C()
     {
         //TODO
     }
     public  function  Method()
     {
         //TODO
         echo  'c' ;
     }
}
 
$a = new  A();
$a ->Method();
 
?>
上面代码可以看出:A类 依赖B类和C类,也就是说,如果今后开发过程中,要对B类或者C类修改,一旦涉及函数改名,函数参数数量变动,甚至整个类结构的调整,我们也要对A类做出相应的调整,A类的独立性丧失了,这在开发过程中是很不方便的,也就是我们说的“牵一发动全身”,如果两个类是两个人分别写的,矛盾就产生了,万一真的要改动B类和C类,有没有办法,可以不去改动或者尽量少改动A类的代码呢?这里要用到 控制反转(高层模块不应该依赖于底层模块,两个都应该依赖抽象)

方法1 构造器注入(不推荐用)

class  A
{
     public  $b ;
     public  $c ;
     public  function  A( $b , $c )
     {
         $this ->b= $b ;
         $this ->c= $c ;
     }
     public  function  Method()
     {
         $this ->b->Method();
         $this ->c->Method();
     }
}

客户端:

$a = new A(new B(), new C());
$a ->Method();
A类的构造器依赖B类和C类,通过构造器的参数传入,至少实现了一点,就是B类对象b和C类对象c的创建都移至了A类外,所以一旦B类和C类发生改动,A类无需做修改,只要在client类里改就可以了,假如有一天,我们需要扩充B类,做两个B类的子类
class  B
{
     public  function  B()
     {
         //TODO
     }
     public  function  Method()
     {
         //TODO
         echo  'b' ;
     }
}

class  B1  extends  B
{
     public  function  B1()
     {
         //TODO
     }
     public  function  Method()
     {
         echo  'b1' ;
     }
}

class  B2  extends  B
{
     public  function  B2()
     {
         //TODO
     }
     public  function  Method()
     {
         echo  'b2' ;
     }
}

也很简单,客户端写法:

$a = new  A( new  B2(), new  C());
$a ->Method();
所以A类是不用关心B类到底有哪些个子类的,只要在客户端类关心就可以了

方法2 工厂模式注入(推荐使用)

class  Factory
{
     public  function  Factory()
     {
         //TODO
     }

     public  function  create( $s )
     {
         switch ( $s )
         {
             case  'B' :
             {
                 return  new  B();
                 break ;
             }
             case  'C' :
             {
                 return  new  C();
                 break ;
             }
             default :
             {
                 return  null;
                 break ;
             }
         }
     }
}

A类代码改为:

class  A
{
     public  $b ;
     public  $c ;
     public  function  A()
     {
         //TODO
     }
     public  function  Method()
     {
         $f = new  Factory();
         $this ->b= $f ->create( 'B' );
         $this ->c= $f ->create( 'C' );
         
         $this ->b->Method();
         $this ->c->Method();
         
         //TODO
     }
}
其实已经解耦了一小部分,至少如果B类和C类的构造函数要是发生变化,比如修改函数参数等,我们只需要改Factory类就可以了
抽象不应该依赖于细节,细节应该依赖于抽象

把B类和C类中的方法再抽象出来,做一个接口

interface  IMethod
{
     public  function  Method();
}

这样,A类中的c变量就不再是一个具体的变量了,而是一个抽象类型的变量,不到运行那一刻,不知道他们的Method方式是怎么实现的

class  B  implements  IMethod
{
     public  function  B()
     {
         //TODO
     }
     public  function  Method()
     {
         //TODO
         echo  'b' ;
     }
}
 
class  C  implements  IMethod
{
     public  function  C()
     {
         //TODO
     }
     public  function  Method()
     {
         //TODO
         echo  'c' ;
     }
}

总结

1.我们把A类中的B类对象和C类对象的创建移至A类外
2.原本A类依赖B类和C类,现在变成了A依赖Factory,Factory依赖B和C
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
控制反转(Inversion of Control,IoC)和依赖注入(Dependency Injection,DI)是面向对象编程中的两个重要概念。它们可以帮助我们更好地实现代码的松耦合,提高代码的可维护性和可扩展性。 IoC是一种编程思想,它将程序的控制权从程序员手中转移到了IoC容器中,由IoC容器来管理和调用对象之间的依赖关系。IoC容器就像是一个工厂,它负责创建和管理对象,程序员只需要告诉IoC容器需要哪些对象,IoC容器就会根据配置文件或者注解等方式来创建对象,并将它们组合起来。 DIIoC的一种具体实现方式,它通过构造函数、属性或者方法等方式将依赖关系注入到对象中。当一个对象需要另一个对象时,它不会直接创建这个对象,而是通过IoC容器来获取这个对象。通过DI,我们可以实现对象之间的松耦合,提高代码的可维护性和可测试性。 下面是一个简单的例子,演示如何使用IoC容器DI实现对象之间的依赖注入: ```java // 定义接口 public interface MessageService { void send(String message); } // 实现接口 public class EmailService implements MessageService { public void send(String message) { System.out.println("Email sent: " + message); } } // 定义需要依赖注入的类 public class MyClass { private MessageService messageService; // 通过构造函数注入依赖 public MyClass(MessageService messageService) { this.messageService = messageService; } public void doSomething() { // 使用依赖的方法 messageService.send("Hello World!"); } } // 使用IoC容器创建对象并注入依赖 public class Main { public static void main(String[] args) { // 创建IoC容器 ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); // 从IoC容器中获取对象 MyClass myClass = (MyClass) context.getBean("myClass"); // 调用方法 myClass.doSomething(); } } ``` 在上面的例子中,我们定义了一个MessageService接口和一个EmailService实现类。然后我们定义了一个MyClass类,它需要依赖MessageService对象来完成一些操作。通过构造函数注入依赖,我们可以将MessageService对象注入到MyClass中。最后,在使用IoC容器创建对象时,我们可以通过配置文件或者注解等方式来指定依赖的实现类,IoC容器会自动创建对象并注入依赖。 总之,IoCDI是非常重要的编程思想,它们可以帮助我们更好地管理对象之间的依赖关系,提高代码的可维护性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值