为啥使用闭包进行赋值而不是直接为键赋值?

Posted

技术标签:

【中文标题】为啥使用闭包进行赋值而不是直接为键赋值?【英文标题】:Why use a closure for assignment instead of directly assigning a value to a key?为什么使用闭包进行赋值而不是直接为键赋值? 【发布时间】:2017-09-23 10:50:37 【问题描述】:

我在看this video,在 7:10 他正在添加一个 db 依赖项并使用闭包来分配值。

我的问题是为什么不直接使用直接赋值,我的意思是不这样做:

$container['db'] = $capsule;

相当于这样做:

$container['db'] = function ($container) use ($capsule) 
    return $capsule;

如果不是,有什么区别,哪种方式更好?

【问题讨论】:

【参考方案1】:

TLDR 这是因为将依赖项定义为闭包使得依赖注入容器可以按需构建它们,因此您无需担心它们的定义顺序和手动管理它们的依赖项。

Pimple 是Dependency Injection Container,它应该可以通过简单方便的方式管理对象的依赖关系来帮助您设置对象。

如果您直接为键分配值,Pimple 会将该值称为参数,当您需要访问该键时,它只会返回该确切值:

$container['sample-param'] = 'foo';
echo $container['sample-param'];
//output: foo

但重点是,这个sample-param 不需要任何设置,它只是一个值,我们可以接受。但假设以下服务设置:

$container['myService'] = function($c) 
    $service = new \Some\Complicated\Service();
    //this service depends on cache service
    $service->setCache($c['cache']);
    return $service;


$container['cache'] = function($c) 
    $cache = new \Cache\Driver();
    //our cache driver needs a db connection
    $cache->setDbConnection($c['db']);
    return $cache;


$container['db'] = function($c) 
    //our database connection requires some parameters
    $dbConnection = new \Database\Connection($c['db-config']);
    return $dbConnection;


$container['db-config'] = [
    'username' => 'foo',
    'password' => 'bar',
    'host' => 'localhost'
];

//Now we want to user our service:
$container['myService']->doSomething();

请注意我在容器中定义不同键的顺序。

myService 需要 cache 但缓存定义在 myService 定义之后。这就是 Pimple 提供帮助的地方,这也是我们将容器传递给每个闭包的原因,因为 Pimple 将按需构建我们的依赖项。当我们需要访问myService时,Pimple 会查看它的内部数据存储,如果它之前已经成功构建并存储了myService,它将返回相同的实例,否则它将调用我们的闭包来构建它。当我们的闭包被调用时,它会要求 Pimple($c 是 Pimple 容器)给它它的 dependecies(在本例中是 cache 服务)。 Pimple 在缓存上应用相同的东西,如果它还没有构建,它会构建它等等......直到它到达需要像db-config 这样的简单参数的部分,它将立即返回。在这个闭包调用链中,我们的对象及其所有依赖项都被构建了。

现在想象一下,如果我们使用简单的值而不是闭包会发生什么?在这种情况下,当我们想要构建 myService 我们 必须处理它的依赖关系。我们必须确保定义服务本身之前定义它的依赖关系,并且我们必须处理与管理依赖关系相关的其他问题。在那种情况下,我们不能只定义我们的myService 并且假设有一个cache 服务可用,稍后将定义。

【讨论】:

【参考方案2】:

在该视频中,他使用 Slim 框架作为容器,默认情况下使用 Pimple 作为容器。

http://pimple.sensiolabs.org/

在该页面上的文档中提到以下内容

定义服务 服务是作为一部分做某事的对象 一个更大的系统。服务示例:数据库连接、 模板引擎或邮件程序。几乎任何全局对象都可以是 服务。

服务由返回一个实例的匿名函数定义 一个对象:

// define some services
$container['session_storage'] = function ($container) 
    return new SessionStorage('SESSION_ID');
;

$container['session'] = function ($container) 
    return new Session($container['session_storage']);
;

注意匿名函数可以访问当前容器实例,允许引用其他服务或参数。

按照设计,容器期望调用函数

Pimple 的容器实现了\ArrayAccess 接口(seen here),这意味着它们可以使对象表现得像一个数组,但不一定是一个数组。在源代码中你会注意到offsetSetoffsetGet,当你这样做时会发生什么:

$container['a'] = function($c) return new Blah();

$thing = $container['a']; 

tl;dr 这是 Pimple 容器特有的东西,而不是 php 特有的东西。

【讨论】:

以上是关于为啥使用闭包进行赋值而不是直接为键赋值?的主要内容,如果未能解决你的问题,请参考以下文章

为啥将 Collections.emptySet() 与泛型一起使用在赋值中而不是作为方法参数?

为啥你可以在函数中使用另一种方法而不是赋值运算符来更改 Ruby 中局部变量的值?

为啥我应该更喜欢 unsafe_unretained 限定符而不是为弱引用属性赋值? [复制]

为啥不能用赋值语句将一个字符串常量直接赋给一个字符数组

在C语言中能否直接给指针指向的数据赋值?为啥?

C语言指针变量为啥要赋初值?