将多个参数注入构造函数是否是一种不好的实践?

6
我正在开发一个相当复杂的物流管理系统,它将不断扩展为几个其他与ERP相关的模块。因此,我正在尽力实现SRP和Open/Close原则,以便于扩展和基于域的管理。
因此,我决定使用Laravel和以下模式(不确定是否有名称):
我将使用PRODUCT对象作为示例。 一个对象/实体/域具有一个类 class ProductService {}
这个类有一个服务提供者,包含在提供者数组中,并且也是自动加载的: ProductServiceServiceProvider 服务提供者实例化(制作)了一个接口ProductRepository。 该接口目前具有MySQL(和一些Eloquent)称为EloquentProductRepository的实现和一个ProductRepositoryServiceProvider绑定了该实现,该实现也被加载并在提供者数组中。
现在,一个产品具有许多不同的属性和与其他领域的关系,因为其他领域(或实体)需要完全分离,并再次遵守上述原则(SRP等),我决定对它们也采用与产品相同的结构……我知道有些人可能认为这太多了,但我们需要使系统非常可扩展,老实说,我喜欢有组织地统一模式(这不需要太多时间,但可以节省很多时间)。
我的问题是,处理产品所有业务逻辑并使“产品”成为它所拥有的东西的ProductService在创建实例时将注入几个依赖项。
这是它目前拥有的:
namespace Ecommerce\Services\Product;

use Ecommerce\Repositories\Product\ProductRepository;
use Ecommerce\Services\ShopEntity\ShopEntityDescriptionService;
use Content\Services\Entity\EntitySeoService;
use Content\Services\Entity\EntitySlugService;
use Ecommerce\Services\Tax\TaxService;
use Ecommerce\Services\Product\ProductAttributeService;
use Ecommerce\Services\Product\ProductCustomAttributeService;
use Ecommerce\Services\Product\ProductVolumeDiscountService;
use Ecommerce\Services\Product\ProductWeightAttributeService;
use Ecommerce\Services\Product\ProductDimensionAttributeService;

/**
 * Class ProductService
 * @package Ecommerce\Services\Product
 */
class ProductService {

    /**
     * @var ProductRepository
     */
    protected $productRepo;

    /**
     * @var ShopEntityDescriptionService
     */
    protected $entityDescription;

    /**
     * @var EntitySeoService
     */
    protected $entitySeo;

    /**
     * @var EntitySlugService
     */
    protected $entitySlug;

    /**
     * @var TaxService
     */
    protected $tax;

    /**
     * @var ProductAttributeService
     */
    protected $attribute;

    /**
     * @var ProductCustomAttributeService
     */
    protected $customAttribute;

    /**
     * @var ProductVolumeDiscountService
     */
    protected $volumeDiscount;

    /**
     * @var ProductDimensionAttributeService
     */
    protected $dimension;

    /**
     * @var ProductWeightAttributeService
     */
    protected $weight;

    /**
     * @var int
     */
    protected $entityType = 3;


    public function __construct(ProductRepository $productRepo, ShopEntityDescriptionService $entityDescription, EntitySeoService $entitySeo, EntitySlugService $entitySlug, TaxService $tax, ProductAttributeService $attribute, ProductCustomAttributeService $customAttribute, ProductVolumeDiscountService $volumeDiscount, ProductDimensionAttributeService $dimension, ProductWeightAttributeService $weight)
    {
        $this->productRepo = $productRepo;
        $this->entityDescription = $entityDescription;
        $this->entitySeo = $entitySeo;
        $this->entitySlug = $entitySlug;
        $this->tax = $tax;
        $this->attribute = $attribute;
        $this->customAttribute = $customAttribute;
        $this->volumeDiscount = $volumeDiscount;
        $this->dimension = $dimension;
        $this->weight = $weight;
    }
`

在PHP中将许多参数传递给构造函数是否是不好的实践(请忽略服务的长名称,因为当确定ERP命名空间时这些名称可能会更改)?
如Ben下面所回答的那样,在这种情况下并不是不好的实践。我的问题与OOP无关,而更多地涉及性能等等。原因是这个特定的类ProductService就像Web开发人员在控制器中所做的一样,即他们可能会(和违反原则)在一个ProductController中添加所有的DB关系,该控制器处理资源库服务(db等),并附加关系,然后它突然变成了您的业务逻辑。
在我的应用程序中(我认为大多数应用程序都是这样的),Web层只是另一层。MVC负责Web层,有时还包括其他API,但是我在我的MVC中除了与视图和JS框架相关的逻辑之外没有任何逻辑。所有这些都在我的软件中。
总之:我知道这是一个非常SOLID的设计,依赖项被注入,并且它们确实是依赖项(即产品必须具有税收,并且产品确实具有重量等),并且由于接口和ServiceProvider,它们可以轻松地与其他类交换。现在由于答案,我也知道在构造函数中注入这么多依赖项是可以的。
最终,我会写一篇关于我使用的设计模式以及为什么在不同场景下使用它们的文章,所以如果您感兴趣,请关注我。
谢谢大家

1
如果你发现你在构造函数中注入了太多的参数,那么现在可能是时候开始看依赖注入容器了...看一下Laravel的IOC容器 - Mark Baker
6
@Mark Baker: ...或者现在是时候开始考虑这个班级依赖的东西太多了。 - zerkms
1
我不认为这是正确的,如果一个类需要依赖于一个特定的具体类,那么它应该进行依赖注入。在我看来,所有良好编写的PHP代码都应该遵循SOLID原则。使用Laravel的IoC容器可以更加轻松地实现这一点。 - David Barker
1个回答

4
通常情况下,不,这不是不良实践,在大多数情况下。但在你的情况下,正如@zerkms在评论中所说,看起来你的类依赖于很多依赖项,你应该研究一下,并考虑如何最小化这些依赖项,但如果你确实在使用它们并且它们应该存在,那么我完全看不出有任何问题。

然而,你应该使用依赖注入容器(DIC)。

一个依赖注入容器,基本上是一种工具,通过提供的命名空间创建类,并包括所有的依赖项创建实例。你也可以分享对象,所以在创建依赖时不会创建它的新实例

我建议你使用Auryn DIC

用法:

 $provider = new Provider();
 $class = $provider->make("My\\App\MyClass");

这里发生的是:
namespace My\App;

use Dependencies\DependencyOne,
    Dependencies\DependencyTwo,
    Dependencies\DependencyThree;

class MyClass {

    public function __construct(DependencyOne $one, Dependency $two, DependencyThree $three) {
         // .....
    }
}

基本上,Provider#make(namespace)创建给定命名空间的实例,并创建所需的构造函数参数及其所有参数的构造函数参数等。

4
为什么你要倡导使用Auryn,Laravel已经自带了IoC容器。 - Joseph Silber
谢谢Ben。我想指出,特别是这个类确实有这些依赖关系,它们共同构成了产品。我正在使用Laravel IoC和服务提供者类来注入实例。我不喜欢在类内部创建新实例,因为我认为这并不是一个好的做法。实例可能已经存在,因此必须重新构建它的属性等。我将在问题中添加更多信息,以便其他人可以更好地理解这种模式。同时感谢您回答我的问题。 - Keith Mifsud
1
@JosephSilber 我并不是很喜欢Laravel,因为据我所见,它通过静态方法创建和绑定。我一直被教导在php中基本上忘记静态方法,并尽可能避免使用它们。 - Artemkller545
4
@BenBeri - 你错了。Laravel并没有通过静态方法来绑定任何东西。它只是提供代理类(即所谓的门面),以便通过静态方式访问底层类。控制反转容器本身不会通过静态方法绑定任何内容。 - Joseph Silber
静态全局访问。这是一堆废话。Auryn是一个很棒的框架无关工具。 - Jimbo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接