文档章节

依赖反转 和 依赖注入 (PHP)

dubox
 dubox
发布于 2017/11/25 02:48
字数 1111
阅读 29
收藏 0

读了文章 《深入探討依賴注入 》做下笔记和总结

文章主要说了这样一个思想进阶过程:

传统实现 —— Interface(依赖反转) —— 工廠模式 —— Constructor Injection(依赖注入) —— Method Injection

 

1.传统实现

class ShippingService
{
    /**
     * @param string $companyName
     * @param int $weight
     * @return int
     * @throws Exception
     */
    public function calculateFee($companyName, $weight)
    {
        if ($companyName == 'BlackCat') {
            $blackCat = new BlackCat();
            return $blackCat->calculateFee($weight);
        }
        elseif ($companyName == 'Hsinchu') {
            $hsinchu = new Hsinchu();
            return $hsinchu->culateFee($weight/1000);//$weight的单位是 g 而这里需要 kg
        }
        elseif ($companyName == 'PostOffice') {
            $postOffice = new PostOffice();
            return $postOffice->getFee($weight);
        }
        else {
            throw new Exception('No company exception');
        }
    }
}

文章使用了算邮费的例子,这样 ShippingService 就要依赖三种邮费计算 class ,文章中的例子还是比较整齐的,而实际情况可能更糟:

因为三种邮费计算 class 可能不是一个人写的 函数名可能不一样 参数个数和类型也可能不一样。。。就像上面的例子(对原文的例子稍有修改)

2.Interface(依赖反转)

//定义接口
interface LogisticsInterface
{
    /**
     * @param int $weight
     * @return int
     */
    public function calculateFee($weight);
}

//实现接口
class BlackCat implements LogisticsInterface
{
    /**
     * @param int $weight
     * @return int
     */
    public function calculateFee($weight)
    {
        return 100 * $weight * 10;
    }
}

使用了Interface之后就可以写成:

class ShippingService
{
    
    public function calculateFee($companyName, $weight)
    {
        switch ($companyName) {
            case 'BlackCat':
                $logistics = new BlackCat();
            case 'Hsinchu':
                $logistics = new Hsinchu();
            case 'PostOffice':
                $logistics = new PostOffice();
            default:
                throw new Exception('No company exception');
        }
        //有了统一的接口 就可以统一调用
        return $logistics->calculateFee($weight);
    }
}

这样三种邮费类依赖Interface对外提供相同的接口 而ShippingService也依赖于Interface不用担心邮费类发生变化 从而实现了依赖反转

但是 ShippingService 依然要 new 三种邮费出来,依赖于Interface ,邮费类虽然不会变化 但是可能会 去掉或增加 

3.工廠模式

class LogisticsFactory
{

    public static function create(string $companyName)
    {
        switch ($companyName) {
            case 'BlackCat':
                return new BlackCat();
            case 'Hsinchu':
                return new Hsinchu();
            case 'PostOffice':
                return new PostOffice();
            default:
                throw new Exception('No company exception');
        }
    }
}

class ShippingService
{

    public function calculateFee($companyName, $weight)
    {
        $logistics = LogisticsFactory::create($companyName);
        return $logistics->calculateFee($weight);
    }
}

使用工厂模式后 业务层(ShippingService)已经完全和那三个讨厌的家伙说拜拜了 从此完全实现了依赖反转

而即便是这样 ShippingService 也还是同时依赖了工厂类和 interface ,同时也像文章说的还有单元测试的问题, 即程序的5个目标:

  1. 容易維護 
  2. 容易新增功能 
  3. 容易重複使用 
  4. 容易上Git,不易與其他人衝突 
  5. 容易寫測試 

中的第5条还没有实现;况且加一层工厂类显得有点多余(至少在本例中)

在实际开发中情况是这样的:

当两个人合作开发,一个人负责ShippingService 另一个人负责三种邮费计算类(当然工厂类也应该由他来实现);当 ShippingService 开发完成之后 而邮费计算这边还没有完成,这时候要对ShippingService做单元测试,你可以根据 interface 模拟一个邮费计算类,但是你不知道工厂类做了什么,所以就没有办法完全模拟出所依赖模块的行为。

-------------------------------------------------------

其实,由于 PHP 的 interface 只定义了输入 没有限制输出,导致 interface 的行为还是有不确定性

4.Constructor Injection(依赖注入) 

class ShippingService
{
    /** @var LogisticsInterface */
    private $logistics;

    /**
     * ShippingService constructor.
     * @param LogisticsInterface $logistics
     */
    public function __construct(LogisticsInterface $logistics)
    {
        $this->logistics = $logistics;
    }

    /**
     * @param int $weight
     * @return int
     */
    public function calculateFee($weight)
    {
        return $this->logistics->calculateFee($weight);
    }
}

将第三方依赖(邮费计算类)通过参数传入,并指定类型;摒弃了工厂类 实现了最大程度的解耦(只依赖于 interface

5.Method Injection

class ShippingService
{
    /**
     * @param LogisticsInterface $logistics
     * @param int $weight
     * @return int
     */
    public function calculateFee(LogisticsInterface $logistics, $weight)
    {
        return $logistics->calculateFee($weight);
    }
}

这一步主要是解决了原文所说的:“只要 class 要實現依賴注入,唯一的管道就是 constructor injection,若有些相依物件只有單一 method 使用一次,也必須使用 constructor injection,這將導致最後 constructor 的參數爆炸而難以維護的问题并且不必再使用 constructor 与 field,使程序更加精簡

------------------------------------------------------------------

原文中的一段精髓,认为概括的很好:

  • 當A class 去 new B class,A 就是高階模組,B就是低階模組。
  • 高階模組不應該去 new 低階模組,也就是 class,而應該由高階模組定義 interface。

  • 高階模組只依賴自己定義的 interface,而低階模組也只依賴 (實踐) 高階模組所定義的 interface。

 简单说,interface 就是一个 标准 上下游环节都按照该标准完成各自的工作。

© 著作权归作者所有

共有 人打赏支持
dubox
粉丝 3
博文 97
码字总数 27918
作品 0
西安
程序员
私信 提问
依赖注入的简单记录

想理解php依赖注入和控制反转两个概念,就必须搞清楚如下的问题: DI——Dependency Injection 依赖注入 IoC——Inversion of Control 控制反转 1、参与者都有谁?  IOC/DI容器就是一个全局...

金于虎
2016/12/23
4
1
对依赖注入和控制反转的一点理解

1.1、IoC是什么 Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制...

舒小贱
2017/12/06
0
0
laravel依赖注入和控制反转

依赖注入与控制反转 依赖注入 当我第一次接触这个词的时候,我是有些丈二和尚摸不着头脑的,至今我也是感到比较困惑的,所以今天我们来探索一下Laravel中的, 来好好的理解它。 控制反转 第一...

eatnothing
2016/05/15
525
0
控制反转( IoC)和依赖注入(DI)

控制反转( IoC)和依赖注入(DI) tags: 容器 依赖注入 IOC DI 控制反转 引言:如果你看过一些框架的源码或者手册,像是laravel或者tp5之类的,应该会提到容器,依赖注入,控制反转等词汇。...

相思叶
03/29
0
0
PHP的依赖注入(DI) 和 控制反转(IoC)

简介 依赖注入和控制反转说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合 优势(为什么使用) 使用依赖注入,最重要的一点好处就是有效的分离了对象和它所需...

ganfanghua
01/12
1
0

没有更多内容

加载失败,请刷新页面

加载更多

性能优化(JVM概念及配置)

虚拟机组成 一次编写,到处运行 Java虚拟机是对操作系统的模拟,隔离差异 2018编程语言排行榜 一个复杂的构架 虚拟机的内部概念 运行原理 编译器,解释器执行流程 内存分配-线程模型 栈帧模型...

这很耳东先生
4分钟前
0
0
Scala之初步认识与环境准备

1. 了解 Scala 1.1. 什么是 Scala Scala 是 Scalable Language 的简写,是一门多范式的编程语言。 Scala设计的初衷是要集成面向对象编程和函数式编程的各种特性。Scala运行于Java平台(Java虚...

飞鱼说编程
25分钟前
2
0
Vue项目分环境打包的实现步骤

在项目开发中,我们的项目一般分为开发版、测试版、Pre版、Prod版。Vue-cli的默认环境一只有dev和prod两个,之前每次要发布测试版或Pre版都是修改了源码中API地址后打包,这样很麻烦。如果能...

peakedness丶
25分钟前
2
0
vue+lowdb+express

搭建流程: 1.安装Node.js; 2.安装npm; 3.安装Express; 搭建流程 npm install -g express 安装 express 生成器 npm install -g express-generator 查看是否安装成功,随便输的一个命令...

Js_Mei
31分钟前
2
0
Qt那些事0.0.15

以下与Q_DECLARE_METATYPE相关内容全部是翻译自Qt文档。参看QVariant Class | Qt Core 5.9以及QMetaType Class | Qt Core 5.9 QVariant QVariant::fromValue(const T& value) 返回一份包含v......

Ev4n
31分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部