服务中的许多依赖项



我在服务层的应用程序中遇到依赖关系问题。

我有以下课程:

   <?php
class UserService{
    private $userRepository;
    private $vocationService;
    private $roleService;
    public function __construct(UserRepository $userRepository, VocationService $vocationService, RoleService $roleService)
    {
        $this->userRepository = $userRepository;
        $this->vocationService = $vocationService;
        $this->roleService = $roleService;
    }

} 

我只注入了三个依赖项。假设,我想添加下一个依赖项,例如:NextService。我的建设者会再次成长。

如果我想在构造函数中传递更多的依赖项,该怎么办?

也许我应该通过传递IoC容器来解决这个问题,然后获得所需的类?这里有一个例子:

<?php
class UserService{
    private $userRepository;
    private $vocationService;
    private $roleService;
    public function __construct(ContainerInterface $container)
    {
        $this->userRepository = $container->get('userRepo');
        $this->vocationService = $container->get('vocService');
        $this->roleService = $container->get('roleService');
    }
} 

但现在我的UserService类依赖于我正在注入的IoC容器。

如何按照良好做法解决问题?

此致,Adam

由于多种原因,将容器作为服务的依赖项注入被认为是一种糟糕的做法。我认为这里的要点是找出原因,然后尝试理解导致您考虑将"注入容器"作为可能的解决方案的问题,以及如何解决这个问题。

在面向对象编程中,明确定义对象之间的关系很重要。当您查看给定的对象依赖关系时,应该通过查看其公共API直观地了解对象的行为以及它所依赖的其他对象。

让您的对象依赖依赖依赖解析程序也是一个坏主意。在您共享的示例中,您的对象离不开DI组件提供的container。如果你想在其他地方使用该对象,例如在使用另一个框架的应用程序中,你就必须重新思考对象获取依赖关系的方式并对其进行重构

这里的主要问题是理解为什么您的服务需要所有这些依赖项,

在面向对象程序设计中,单一责任原则声明每个上下文(类、函数、变量等)定义一个单一的责任,并且该责任应该完全由上下文封装。其所有服务应与这一责任勉强一致。来源:维基百科

基于这个定义,我认为您应该将UserService划分为服务,每个服务只处理一个责任。

  • 例如,一种获取用户并将其保存到数据库的服务
  • 例如,另一个管理角色的服务
  • 。。。等等

我同意__construct可以很容易地增长。

但是,您可以使用Setter DI:http://symfony.com/doc/current/components/dependency_injection/types.html#setter-注入

Morover,有一个Property DI,但我不建议使用它,因为它让您的服务非常容易被操纵:http://symfony.com/doc/current/components/dependency_injection/types.html#property-注入

您可以在一个助手服务中抽象一些常用的服务,然后将该助手注入到其他服务中。此外,您还可以在此助手服务中定义一些有用的函数。类似这样的东西:

<?php 
namespace SamanLibraryService;
use SymfonyBundleFrameworkBundleRoutingRouter;
use SymfonyComponentFormFormFactory;
use SymfonyBundleFrameworkBundleTranslationTranslator;
use SymfonyBundleTwigBundleDebugTimedTwigEngine;
use SymfonyComponentSecurityCoreSecurityContext;
use DoctrineORMEntityManager;
class HelperService 
{
  protected $translator;
  protected $securityContext;
  protected $router;
  protected $templating;
  protected $em;
    public function __construct(
        Translator $translator, 
        SecurityContext $securityContext,
        Router $router,
        TimedTwigEngine $templating,
        EntityManager $em
        ) 
    {
        $this->translator = $translator;
        $this->securityContext = $securityContext;
        $this->router = $router;
        $this->templating = $templating;
        $this->em = $em;
    }
    Getters ...
    public function setParametrs($parameters)
    {
        if (null !== $parameters) {
            $this->parameters = array_merge($this->parameters, $parameters);
        }
        return $this;
    }
    /**
     * Get a parameter from $parameters array
     */
    public function getParameter($parameterKey, $defaultValue = null)
    {
        if (array_key_exists($parameterKey, $this->parameters)) {
            return $this->parameters[$parameterKey];
        }
        return $defaultValue;
    }
}

现在假设你有一个UserService,然后你这样定义它:

<?php
namespace SamanUserBundleService;
use SamanLibraryServiceHelperService;
class UserService
{
  protected $helper;
  public function __construct(
     Helper $helper,
     $parameters
     ) 
  {
     $this->helper = $helper;
     $this->helper->setParametrs($parameters);
  }
  public function getUser($userId)
  {
     $em = $this->helper->getEntityManager();
     $param1 = $this->helper->getParameter('param1');
     ... 
  }

这个例子是为Symfony 4创建的,但原理应该在旧版本中有效

正如其他人所提到的,最好对应用程序进行设计,以限制每个服务的功能范围,并减少每个消费类上的注入次数。

如果你确实需要多次注射,以下方法会有所帮助,但如果你在许多地方注射服务,这也是一种减少样板的好方法。

考虑一个要注入AppControllerMyController:的服务AppServicesMyService

为您的服务创建一个"Injector"特性。

<?php
// AppServicesMyService
namespace AppDependencyInjection;
use AppServicesMyService;
trait InjectsMyService
{
    /** @var MyService */
    protected $myService;
    /**
     * @param MyService $myService
     * @required
     */
    public function setMyService(MyService $myService): void
    {
        $this->myService = $myService;
    }
}

控制器内部:

<?php
namespace AppController;
class MyController
{
    use AppDependencyInjectionInjectsMyService;
    ...
    public myAction()
    {
        $this->myService->myServiceMethod();
        ...
    }
    ...
}

这样:

  • 一行代码将使您的服务在任何容器托管类中都可用,如果您在许多地方使用服务,这将非常方便
  • 搜索注入器类可以很容易地找到服务的所有用法
  • 没有魔法方法
  • IDE将能够自动完成受保护的服务实例属性,并知道其类型
  • 控制器方法签名变得更简单,只包含参数

如果你有多次注射:

<?php
namespace AppController;
use AppDependencyInjection as DI;
class SomeOtherController
{
    use DIInjectsMyService;
    use DIInjectsMyOtherService;
    ...
    use DIInjectsMyOtherOtherService;
    ...
}

您还可以为框架提供的服务创建注入器,例如条令实体管理器:

<?php
namespace AppDependencyInjection;
use DoctrineORMEntityManagerInterface;
trait InjectsEntityManager
{
    /** @var EntityManagerInterface */
    protected $em;
    /**
     * @param EntityManagerInterface $em
     * @required
     */
    public function setEm(EntityManagerInterface $em): void
    {
        $this->em = $em;
    }
}
class MyClass
{
   ...
   use AppDependencyInjectionInjectsEntityManager;

最后一点:我个人不会试图让这些注射器比我概述的更智能。试图制作一个多态注入器可能会混淆代码,并限制IDE自动完成和了解服务类型的能力。

相关内容

  • 没有找到相关文章

最新更新