让DI容器替换全局$注册表对象是一种好习惯吗?

ste*_*lin 9 php unit-testing dependency-injection

我已经开始重构一个小应用程序来使用一个小的DI容器,而不是使用$ registry :: getstuff(); 在我的课程中调用我将它们注入容器中.

这提出了两个问题,

Q1 - >我扩展了Pimple DI类并创建了一个容器,其中包含特定于需要DI的每个对象的依赖项.然后我将整个shebang对象提供它,并在构造函数中将其解析为将DI的对象分配给我正在构建的对象的类属性.

我应该在新对象()调用中分离对象吗?我刚刚发现这样比较容易,但现在看到我是一个单人团队,我只想确认我有适当的方法.

Q2 - >我发现如果我在几个主要类上执行此操作,我遍布的$ registry对象将是无效的,这是否是使用DI的正常结果,没有更多的注册表?我可能在容器中注入了一个或两个单例,但它看起来就像我将需要的一样,甚至那些可以很容易地被淘汰,因为DI有一个share()属性,它返回对象的同一个实例,有效地消除了需要对于单身人士.这是摆脱应用程序需要注册表/单身人士的方式,因为如果它是这样的很容易.

Gér*_*oës 20

Q2:如果你在你的$registry对象周围传遍......那么你的注册表实际上不是所谓的注册表(正如福勒所描述的那样).

一个注册或多或少是一个全局对象(一个"知名")与get/set方法.在PHP中,为的实现两个共同的原型注册

作为一个单身人士

class RegistryAsSingleton
{
    public static function getInstance (){
       //the singleton part
    }

    public function getStuff ()
    {
       //some stuff accessed thanks to the registry
    }
}
Run Code Online (Sandbox Code Playgroud)

使用静态方法到处都是

class RegistryAsStatic
{
    public static function getStuff()
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

将您的注册表遍布整个地方使它成为一个对象:一个容器,其目的不是提供对其他对象的引用.

您的DI容器(使用您在OP中建议的Pimple)是一种注册表本身:它是众所周知的,使您可以从任何地方获取组件.

所以是的,我们可以说你的DI容器将通过执行相同的功能来删除注册表的要求和必要性.

但是(总有一个但是)

在被证明是无辜的之前,登记处总是有罪(Martin Fowler)

如果您使用DI容器替换您的注册表,这可能是错误的.

例如:

//probably a Wrong usage of Registry
class NeedsRegistry
{
    public function asAParameter(Registry $pRegistry)
    {
       //Wrong dependency on registry where dependency is on Connection
       $ct = $pRegistry->getConnection();
    }

    public function asDirectAccess ()
    {
       //same mistake, more obvious as we can't use another component
       $ct = Registry::getInstance()->getConnection();
    }
}

//probably a wrong replacement for Registry using DI Container
class NeedsContainer
{
    public function asAParameter(Container $pRegistry)
    {
       //We are dependent to the container with no needs, 
       //this code should be dependent on Connection
       $ct = $pContainer->getConnection();
    }

    public function asDirectAccess ()
    {
       //should not be dependent on container
       $ct = Container::getInstance()->getConnection();
    }
}
Run Code Online (Sandbox Code Playgroud)

为什么这么糟糕?因为您的代码依赖程度不低于以前,所以它仍然依赖于一个组件(注册表或容器),它没有提供明确的目标(我们可能会想到这里的接口)

Registry-pattern在某些情况下很有用,因为它是一种简单且相当便宜的方法来定义组件或数据(例如全局配置).

通过删除依赖关系来重构上述示例而不依赖于DI的方法是:

class WasNeedingARegistry
{
    public function asAParameter (Connection $pConnection)
    {
       $pConnection->doStuff();//The real dependency here, we don't care for 
       //a global registry
    }
}

//the client code would be like
$wasNeedingARegistry = new WasNeedingARegistry();
$wasNeedingARegistry->setConnection($connection);
Run Code Online (Sandbox Code Playgroud)

当然,如果客户端代码不知道连接,这可能是不可能的,这可能是您可能首先使用注册表结束的原因.

现在DI开始发挥作用

使用DI使我们的生活变得更好,因为它将处理依赖性并使我们能够在即用状态下访问依赖项.

在代码的某处,您将配置组件:

$container['connection'] = function ($container) {
    return new Connection('configuration');
};
$container['neededARegistry'] = function ($container) {
    $neededARegistry = new NeededARegistry();
    $neededARegistry->setConnection($container['connection']);
    return $neededARegistry;
};
Run Code Online (Sandbox Code Playgroud)

现在,您拥有重构代码所需的一切:

// probably a better design pattern for using a Registry 
class NeededARegistry
{
    public function setConnection(Connection $pConnection)
    {
       $this->connection = $pConnection;
       return $this;
    }

    public function previouslyAsDirectAccess ()
    {
       $this->connection->doStuff();
    }
}

//and the client code just needs to know about the DI container
$container['neededARegistry']->previouslyAsDirectAccess();
Run Code Online (Sandbox Code Playgroud)

"客户端"代码应尽可能隔离.客户端应该负责并注入自己的依赖项(通过set- 方法).客户端不应负责处理其依赖项的依赖项.

class WrongClientCode
{
    private $connection;
    public function setConnection(Connection $pConnection)
    {
       $this->connection = $pConnection;
    }

    public function callService ()
    {
       //for the demo we use a factory here
       ServiceFactory::create('SomeId')
                       ->setConnection($this->connection)
                       ->call();
       //here, connection was propagated on the solely 
       // purpose of being passed to the Service
    }
}

class GoodClientCode
{
    private $service;
    public function setService(Service $pService)
    {
       //the only dependency is on Service, no more connection
       $this->service = $pService;
    }

    public function callService ()
    {
       $this->service->setConnection($this->connection)
                     ->call();
    }
}
Run Code Online (Sandbox Code Playgroud)

DI容器将使用已正确配置其Connection的Service配置GoodClientCode

至于Singleton方面,是的,它可以让你摆脱它们.希望这可以帮助

  • 很棒的解释.我喜欢在Stack上看到糟糕的代码/好的代码示例.快速提问:在哪里将DI容器存储在典型的MVC框架中?您是否只有1定义所有服务/组件?或者你有很多? (2认同)