工厂中的依赖注入

时间:2014-08-22 12:48:57

标签: php oop dependency-injection factory

我是DI的新手,但我真的想尝试使用它。

有些事我不明白。这是一个工厂的简单伪代码,我使用了很多。

class PageFactory {
   public function __construct(/* dependency list */) {
      ... //save reference to the dependencies
   }

   public function createPage($pagename) {
       switch ($pagename) {
           case HomePage::name:
               return new HomePage(/* dependency list */);
           case ContactPage::name:
               return new ContactPage(/* dependency list */);
           ...
           default:
               return null;
       }
   }
}

它有一个非常简单的逻辑,它选择基于字符串的实现实例。它非常有用,因为我可以稍后选择我需要的页面,只会创建一个页面。

我将如何重写此代码,因此我的页面实例将由依赖项容器创建,因此我不需要处理工厂及其创建的页面的依赖项?

我看到的唯一解决方案是制作我想要使用的容器,工厂的依赖项,并从工厂内调用它。我有很多问题。

首先,我不想将容器耦合到我的应用程序以及它拥有的每个工厂中。

其次,我最大的问题是,对容器的调用非常麻烦,它是字符串键入的(即 $ container-> get('Foo'); )。我想尽可能少地使用它。只有一次。

修改

我不想写一个DI容器。我想使用现有的。我的问题是关于使用情况。我将如何使用DI容器代替或在上述工厂中使用DI容器,同时保持逻辑选择实例。

编辑2:

我开始使用Dice作为DI容器,因为它很轻,并且知道我需要的一切。如果我可以在一个地方使用它并构建整个应用程序,我更愿意。为此,我需要一种方法以某种方式摆脱这些工厂,或以某种方式修改它以使这些页面像依赖项一样,因此DI容器将为它们提供实例。

编辑3:

是的,我需要这个用于测试目的。我也是测试的新手,但到目前为止我非常喜欢它。

这些页面是MVC框架调用控制器的。但是我检查的所有MVC框架都没有让它们的控制器可测试,因为它们会自动创建它们的实例。并且因为它们是由系统创建的,所以用户无法自定义其构造函数参数。

有一种简单的方法可以检查任何框架。我只是查看我应该在该特定框架中的控制器中使用数据库的方式。大多数框架都是程序性的,或者使用一些服务定位器,无论哪种方式,它们都是从公共范围获取它们的依赖关系,这是我不想做的事情。这就是我没有自动化控制器实例化的原因。缺点是我现在拥有这些奇怪的工厂,它们具有很多依赖性。我想将此任务替换为DI容器。

大多数框架都实现了自己的测试机制,这更像是功能测试,而不是单元测试,但我也不想这样做。

3 个答案:

答案 0 :(得分:3)

  

注意:依赖注入是一种设计模式,而DI容器是库,它通过利用依赖注入生成实例......或者它们是糟糕的服务定位器,有人销售< / em>作为最新的热门话题

正确实施的DI容器基本上是一个智能工厂&#34;。但实施一个可能会超出你目前的能力。它有点真的很复杂,因为一个好的DI容器构建整个依赖树。

例如:

  

假设您有一个类Foo,需要在构造中传递AlphaBeta的实例。但是有一个问题。 Beta的实例在构造函数中也需要PDO DSN和Cache的实例。

     

一个精心设计的DI容器将能够立即构建整个依赖关系树。

不应自己制作,而应该使用现有的DI容器。

我的建议是Auryn

答案 1 :(得分:0)

EDITED

使用DI容器几天后,我意识到解决方案实际上有多简单,我现在真的很尴尬。它也帮助bad_boy推荐路由。

DI作为路由器输出处理程序

我可以使用DI容器来处理简单路由器的输出。路由器的问题是它们将类名返回给框架,因此它可以通过框架来实例化它们。这是一个问题,因为构造函数将被预定义(或者只是空),并且依赖关系只会来自公共范围或服务定位器。

但是对于DI容器,页面已经由框架而不是用户创建。所以解决方案只是允许存在这样的路由,但让DI框架处理输出。

所以它看起来像这样:

$router = $di->create(Router::class);
$pageClassName = $router->getRequestedPageClassName();
$page = $di->create($pageClassName);
echo $page->render();

这样我就可以在一个地方,在我的应用程序的根目录中使用DI,并且我可以拥有许多包含任何逻辑和依赖关系的路由器,任意数量的页面都有任何依赖关系。

:: class constants

我也遇到了很大的问题。主要是,它们是PHP 5.5。我通过编写一个小的PHP预处理器来解决它,它接受一个PHP文件,将每个ClassName ::类更改为&#34; ClassName&#34;,将其保存到我的IDE看不到的特殊位置,并且我已经解决了这个问题。设置我的自动加载器只加载处理过的PHP文件。现在,我可以在我的PHP 5.3设置中使用:: class常量,只需在.php之前添加一个特殊的扩展名到PHP文件。

答案 2 :(得分:0)

我认为你在某些方面忽略了DI&amp; amp; DIC,其中一个要点是在创建实例时传递容器。

class Router {

   private $loader;
   private $routes

   function __construct($container){
      $this->loader = $container->get('loader');
   }
}

是的,它使用字符串作为标识符。这就是重点,它避免了紧耦合。 创建实例的签名变得更加简单。

如果您希望IDE能够解码属性背后的内容,可以使用docblocks

/**
* @property LoaderInterface $loader
*/ 

那么你如何将它用于工厂?

class NotificationFactory {

   /**
    * @property ContainerInterface $container
    */
   private $container;

   function __construct($container){
     $this->container = $container;
   }

   public function create($user, $message){
     return new Notification(array(
        "to" => $user->email,
        "subject" => $message
     ), 
     $container);
   }
}

很简单,然后在Notification类中你需要一个翻译器:

class Notification {

    /**
    * @property ContainerInterface $container
    */
    private $container;
    /**
    * @property TranslatorInterface $translator
    */
    private $translator;

    function __construct($container){
       $this->container = $container;
       $this->translator = this->container->get('translator');
    }

    function createMessage($message, $lang){
       return $translator->translate($message, $lang);
    }
}

这里的重点是,你可以在链条的任何地方热交换任何组件,这使得测试变得更加容易。

创建控制器时的另一个问题似乎是你错了。

如果您的控制器都具有相同的创建者args:

class FooController {
  function __construct($container, $request, $route){
     /** stuff happening here **/
  }

  function showAction($id) {
    return new Response(/** stuff happening here **/);
  }
}

然后你可以有一个简单的请求匹配器,它可以将请求和控制器/操作结合在一起。

我建议你看一下Symfony2,它有可测试的控制器和一个很容易掌握并作为独立组件使用的路由器,还有Fabian Potenciers的优秀文章What is Dependency Injection?