【问题标题】:How to break and improve cyclic dependency without using proxy pattern?如何在不使用代理模式的情况下打破和改善循环依赖?
【发布时间】:2016-03-31 14:57:40
【问题描述】:

我的课程依赖于太多其他课程,我找不到改进它的方法。问题如下:

我有一个 ProductRepo、ProductFactory 和一个 ImageFactory 类。 ProductRepo 对产品表执行 db 操作并将行作为数组获取。这个数组被传递给 ProductFactory 来创建一个 Product Modal。 产品模式也有链接到它的图像。

客户端代码:

$products = $this->productRepo->findAll('...');
foreach($products as $product){
    ...
    //get images
    $images = $product->getImages();
    ...
}

Class ProductRepo implements ProductRepositoryInterface{
    protected $productFactory;
    protected $imageFactory;
    public function __construct(ProductFactoryInterface $productFactory, ImageFactoryInterface $imageFactory)
    {
        $this->productFactory = $productFactory;
        $this->imageFactory = $imageFactory;
    }

    public function findAll(...)
    {
        $result = $this->execute('....');
        $products = $this->productFactory->make($result);
        return $products;
    }

    public function getImages($productId)
    {
        $result = $this->execute('....');
        $images = $this->imageFactory->make($result);
        return $images;
    }
}

Class ProductFactory implements ProductFactoryInterface{
    protected $productRepo;
    public function __construct(ProductRepositoryInterface $productRepo)
    {
        $this->productRepo = $productRepo;
    }

    public function make($items)
    {
        ...
        $products = [];
        foreach($items as $item){
            $product = new Product($item);
            $item->setImages($this->productRepo->getImages($product->getId()));
            $products[] = $product;
        }
        ...
        return $products;
    }
}

Class ImageFactory implements ImageFactoryInterface{
    public function make($items)
    {
        ...
        $images = [];
        foreach($items as $item){
            $image = new Image($item);
            $images[] = $image;
        }
        ...
        return $images;
    }
}

所以,我有以下问题:

  1. 循环依赖 ProductRepo --> ProductFactory --> ProductRepo

    要跳过这个,我可以使用 setter 注入或使用代理模式。但我认为这不是一个好的解决方案。遇到这样的问题,你们是怎么处理的?

  2. ProductRepo 依赖于 ProductFactory 和 ImageFactory。这是依赖多个工厂的好习惯吗?

我认为问题很清楚。 :) 谢谢

【问题讨论】:

  • 顺便说一句,我正在使用 PHP,并删除了 Factory 类中 ProductHydrator 和 ImageHydrator 实例的实现,以使问题变得简单。

标签: design-patterns solid-principles cyclic-dependency


【解决方案1】:

据我所知,你不需要工厂模式来做你正在做的事情,而不是不同类型的图像和产品类,你只有一个具有不同细节的产品类。

我建议创建一个带有构造函数的单个产品类,该构造函数接收来自产品数据库的单行信息和属于它的图像集合。然后构造函数可以设置产品类。

然后,在产品 repo 类中创建一个产品集合或数组并将其返回。

类似这样的东西(用伪php编写)

    Class Product
    {
        public function __construct(productInfo, imageArray)
        {
            //contruct product here
        }
    }

    Class ProductRepo
    {

        public function getProducts()
        {
            //retrieve products
            $items = getProducts();

            //setup products
            return setupProducts($items);
        }

        private function setupProducts($items)
        {

            foreach($items as $item){
                $images = $this->getImages($product->getId());

                $product = new Product($item, $images);

                $products[] = $product;
        }
            return $products;
        }

        private function getImages($itemId)
        {
            //get and return images for this product
        }

        private function loadProducts()
        {
            //load from database and return all products
        }
    }

工厂模式适用于需要在具体对象中实现具有不同功能的接口的多个实现并且需要一种方法来选择正确的接口的情况。例如,如果您有一个尝试计算各种形状面积的应用程序,您可能有一个带有 calculateArea() 函数的 IShapes 接口和几个实现它的类(例如,圆形、三角形、矩形等)都使用不同的公式来计算形状的面积。然后,您可以使用工厂为一组通用参数构造和获取特定形状名称的正确实现。

编辑: 如果不同产品类型在功能上存在差异,例如奖励积分的计算方式,您可以这样做:

    class ProductFactory
    {
        public Iproduct getProduct($productType, $productInfo, $images)
        {
            switch(productType)
            {
                case: featured
                    return new featuredProduct($productInfo)
                case: standard
                    return new standardProduct($productInfo)
            }
        }
    }

    Interface Iproducts
    {
        //suppose different product types have different reward point formula's
        calculateRewardPoints();

        ....
        //other functions
    }

然后可以像这样在上面的产品仓库中使用它:

    private function setupProducts($items)
    {
        foreach($items as $item){
        $images = $this->getImages($product->getId());

            $product = ProductFactory.getProduct($item.type, $item, $images);

            $products[] = $product;
    }

【讨论】:

  • 感谢您的快速回复。我认为保持工厂很好。例如,如果我需要根据需要创建基本产品或特色产品,那么最好让 productfactory 决定哪个是哪个。你怎么看?
  • 如果你想以这种方式处理它,你会希望你的界面单独抽象每个产品项。普通产品和特色产品在功能上是否有任何不同,或者它们只是以不同的方式显示?如果它们只是以不同的方式显示,您可以使用工厂来选择要使用的局部视图/渲染样式,具体取决于产品是否具有特色,这可能只是产品类的属性。
【解决方案2】:

有几种方法可以打破循环依赖,但最根本的问题似乎是 ProductFactory 需要一个 ProductRepo,它本身必须能够构造产品,即使这个功能不会被使用,也可能不会传递使用不同工厂(隐藏规则)的 ProductRepo 是有意义的。所以:

1) 创建一个只有 getImages 方法的 ImageRepositoryInterface。 ProductRepositoryInterface 可以扩展这个接口或者 ProductRepo 可以独立实现它。然后,将图像存储库传递给 ProductFactoryInterface.make,而不是在构建时要求它。此时您可以传递您的 ProductRepo。

2) 是的,依赖一种以上的工厂没有问题

【讨论】:

  • 感谢 Matt (Y),将 ImageRepo 传递到 ProductFactoryInterface.make 而不是在构造时要求它是一个更好的主意。
猜你喜欢
  • 1970-01-01
  • 2020-01-29
  • 1970-01-01
  • 2016-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-13
相关资源
最近更新 更多