资讯专栏INFORMATION COLUMN

IOC 模式解耦与 Laravel 服务容器

韩冰 / 2968人阅读

摘要:的优美得益于开发的组件式解耦,这与服务容器和服务提供者的理念是离不开的,下篇将用框架中类来梳理服务容器的工作流程。

原文:blog,转载注明来源即可。
本文代码:GitHub

前言

服务容器是 Laravel 框架实现模块化解耦的核心。模块化即是将系统拆成多个子模块,子模块间的耦合程度尽可能的低,代码中尽可能的避免直接调用。这样才能提高系统的代码重用性、可维护性、扩展性。

下边出行例子有火车、飞机两种出行方式,对应给出了 3 种耦合度越来越低的实现:高度耦合实现、工厂模式解耦、IOC 模式解耦。

高度耦合实现 代码实现

定义 TrafficTool 接口并用 Train、Plane 实现,最后在 Traveler 中实例化出行工具后说走就走。代码十分简洁:

travelTool = new Train();
    }

    public function travel() {
        $this->travelTool->go();
    }
}


$me = new Traveler();
$me->travel();

运行:

$ php normal.php
[Travel By]: train
优点

代码十分简洁:一个接口两个类最后直接调用。

缺点

在第 32 行,TravelerTrain 两个组件发生了耦合。以后想坐飞机出行,必须修改 __construct() 的内部实现:$this->travelTool = new Plane();

重用性和可维护性都很差:在实际的软件开发中,代码会根据业务需求的变化而不断修改。如果组件之间直接相互调用,那组件的代码就不能轻易修改,以免调用它的地方出现错误。

工厂模式解耦 工厂模式

分离代码中不变和变的部分,使得在不同条件下创建不同的对象。

代码实现
...

class  TrafficToolFactory
{
    public function create($name) {
        switch ($name) {
            case "train":
                return new Train();
            case "plane":
                return new Plane();
            default:
                exit("[No Traffic Tool] :" . $name);
        }
    }
}


// 旅游者类,使用火车出行
class Traveler
{
    protected $trafficTool;

    public function __construct($toolName) {
        // 使用工厂类实例化需要的交通工具
        $factory = new TrafficToolFactory();
        $this->travelTool = $factory->create($toolName);
    }

    public function travel() {
        $this->travelTool->go();
    }
}

// 传入指定的方式
$me = new Traveler("train");
$me->travel();

运行:

$ php factory.php
[Travel By]: train
优点

提取了代码中变化的部分:更换交通工具,坐飞机出行直接修改 $me = new Traveler("plane") 即可。适用于需求简单的情况。

缺点

依旧没有彻底解决依赖:现在 TravelerTrafficToolFactory 发生了依赖。当需求增多后,工厂的 switch...case 等代码也不易维护。

IOC 模式解耦

IOC 是 Inversion Of Controll 的缩写,即控制反转。这里的“反转”可理解为将组件间依赖关系提到外部管理。

简单的依赖注入

依赖注入是 IOC 的一种实现方式,是指组件间的依赖通过外部参数(interface)形式直接注入。比如对上边的工厂模式进一步解耦:

trafficTool = $tool;
    }

    public function travel() {
        $this->trafficTool->go();
    }
}

$train = new Train();
$me    = new Traveler($train);    // 将依赖直接以参数的形式注入
$me->travel();

运行:

$ php simple_ioc.php
[Travel By]: train
高级依赖注入 简单注入的问题

如果三个人分别自驾游、坐飞机、高铁出去玩,那你的代码可能是这样的:

$train = new Train();
$plane = new Plane();
$car   = new Car();

$a = new Traveler($car);
$b = new Traveler($plane);
$c = new Traveler($train);

$a->travel();
$b->travel();
$c->travel();

看起来就两个字:蓝瘦。上边简单的依赖注入相比工厂模式已经解耦挺多了,参考 Laravel 中服务容器的概念,还能继续解耦。将会使用到 PHP 反射和匿名函数,参考:Laravel 框架中常用的 PHP 语法

IOC 容器

高级依赖注入 = 简单依赖注入 + IOC 容器

binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    /**
     * 生产:执行回调函数
     *
     * @param $abstract     字符指令
     * @param array $params 回调函数所需参数
     * @return mixed        回调函数的返回值
     */
    public function make($abstract, $params = []) {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        // 此时 $this 是有 2 个元素的数组
        // Array (
        //     [0] => Container Object (
        //                [binds] => Array ( ... )
        //                [instances] => Array()
        //            )
        //     [1] => "train"
        // )
        array_unshift($params, $this);

        // 将参数传递给回调函数
        return call_user_func_array($this->binds[$abstract], $params);
    }
}

$container = new Container();
$container->bind("traveler", function ($container, $trafficTool) {
    return new Traveler($container->make($trafficTool));
});

$container->bind("train", function ($container) {
    return new Train();
});

$container->bind("plane", function ($container) {
    return new Plane();
});

$me = $container->make("traveler", ["train"]);
$me->travel();

运行:

$ php advanced_ioc.php
[Travel By]: train
简化并解耦后的代码

那三个人再出去玩,代码将简化为:

$a = $container->make("traveler", ["car"]);
$b = $container->make("traveler", ["train"]);
$c = $container->make("traveler", ["plane"]);

$a->travel();
$b->travel();
$c->travel();

更多参考:神奇的服务容器

Laravel 的服务容器

Laravel 自己的服务容器是一个更加高级的 IOC 容器,它的简化代码如下:

getClosure($abstract, $concrete);
        }
        $this->binds[$abstract] = compact("concrete", "shared");
    }

    // 获取回调函数
    public function getClosure($abstract, $concrete) {
        return function ($container) use ($abstract, $concrete) {
            $method = ($abstract == $concrete) ? "build" : "make";
            return $container->$method($concrete);
        };
    }

    protected function getConcrete($abstract) {
        if (!isset($this->binds[$abstract])) {
            return $abstract;
        }
        return $this->binds[$abstract]["concrete"];
    }


    // 生成实例对象
    public function make($abstract) {
        $concrete = $this->getConcrete($abstract);
        if ($this->isBuildable($abstract, $concrete)) {
            $obj = $this->build($concrete);
        } else {
            $obj = $this->make($concrete);
        }
        return $obj;
    }


    // 判断是否要用反射来实例化
    protected function isBuildable($abstract, $concrete) {
        return $concrete == $abstract || $concrete instanceof Closure;
    }

    // 通过反射来实例化 $concrete 的对象
    public function build($concrete) {
        if ($concrete instanceof Closure) {
            return $concrete($this);
        }
        $reflector = new ReflectionClass($concrete);
        if (!$reflector->isInstantiable()) {
            echo "[can"t instantiable]: " . $concrete;
        }

        $constructor = $reflector->getConstructor();
        // 使用默认的构造函数
        if (is_null($constructor)) {
            return new $concrete;
        }

        $refParams = $constructor->getParameters();
        $instances = $this->getDependencies($refParams);
        return $reflector->newInstanceArgs($instances);
    }


    // 获取实例化对象时所需的参数
    public function getDependencies($refParams) {
        $deps = [];
        foreach ($refParams as $refParam) {
            $dep = $refParam->getClass();
            if (is_null($dep)) {
                $deps[] = null;
            } else {
                $deps[] = $this->resolveClass($refParam);
            }
        }
        return (array)$deps;
    }

    // 获取参数的类型类名字
    public function resolveClass(ReflectionParameter $refParam) {
        return $this->make($refParam->getClass()->name);
    }
}


$container = new Container();

// 将 traveller 对接到 Train 
$container->bind("TrafficTool", "Train");
$container->bind("traveller", "Traveller");

// 创建 traveller 实例
$me = $container->make("traveller");
$me->travel();

运行:

$ php laravel_ioc.php     
[Travel By]: train

Train 类要能被实例化,需要先注册到容器,这就涉及到 Laravel 中服务提供者(Service Provider)的概念了。至于服务提供者是怎么注册类、注册之后如何实例化、实例化后如何调用的... 下节详细分析。

总结

本文用一个旅游出行的 demo,引出了高度耦合的直接实现、工厂模式解耦和 IOC 模式解耦共计三种实现方式,越往后代码量越多还有些绕,但类(模块)之间的耦合度越来越低,最后实现了简化版的 Laravel 服务容器。

Laravel 的优美得益于开发的组件式解耦,这与服务容器和服务提供者的理念是离不开的,下篇将用 Laravel 框架 laravel/framework/src/Illuminate/Container.phpContainer 类来梳理 Laravel 服务容器的工作流程。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/28798.html

相关文章

  • Laravel中的核心概念

    摘要:可以为服务提供者的方法设置类型提示。方法将在所有其他服务提供者均已注册之后调用。所有服务提供者都在配置文件中注册。可以选择推迟服务提供者的注册,直到真正需要注册绑定时,这样可以提供应用程序的性能。 本文最早发布于 Rootrl的Blog 导言 Laravel是一款先进的现代化框架,里面有一些概念非常重要。在上手Laravel之前,我认为先弄懂这些概念是很有必要的。你甚至需要重温下PHP...

    ddongjian0000 评论0 收藏0
  • 详解 Laravel 中的依赖注入和 IoC

    摘要:依赖注入依赖注入一词是由提出的术语,它是将组件注入到应用程序中的一种行为。就像说的依赖注入是敏捷架构中关键元素。类依赖于,所以我们的代码可能是这样的创建一个这是一种经典的方法,让我们从使用构造函数注入开始。 showImg(https://segmentfault.com/img/remote/1460000018806800); 文章转自:https://learnku.com/la...

    haitiancoder 评论0 收藏0
  • 深入剖析 Laravel 服务容器

    摘要:划下重点,服务容器是用于管理类的依赖和执行依赖注入的工具。类的实例化及其依赖的注入,完全由服务容器自动的去完成。 本文首发于 深入剖析 Laravel 服务容器,转载请注明出处。喜欢的朋友不要吝啬你们的赞同,谢谢。 之前在 深度挖掘 Laravel 生命周期 一文中,我们有去探究 Laravel 究竟是如何接收 HTTP 请求,又是如何生成响应并最终呈现给用户的工作原理。 本章将带领大...

    abson 评论0 收藏0
  • php实现依赖注入(DI)和控制反转(IOC)

    摘要:工厂模式,依赖转移当然,实现控制反转的方法有几种。其实我们稍微改造一下这个类,你就明白,工厂类的真正意义和价值了。虽然如此,工厂模式依旧十分优秀,并且适用于绝大多数情况。 此篇文章转载自laravel-china,chongyi的文章https://laravel-china.org/top...原文地址: http://www.insp.top/learn-lar... ,转载务必保...

    tomato 评论0 收藏0
  • 深入理解控制反转(IoC)和依赖注入(DI)

    摘要:本文一大半内容都是通过举例来让读者去理解什么是控制反转和依赖注入,通过理解这些概念,来更加深入。这种由外部负责其依赖需求的行为,我们可以称其为控制反转。工厂模式,依赖转移当然,实现控制反转的方法有几种。 容器,字面上理解就是装东西的东西。常见的变量、对象属性等都可以算是容器。一个容器能够装什么,全部取决于你对该容器的定义。当然,有这样一种容器,它存放的不是文本、数值,而是对象、对象的描...

    HollisChuang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<