You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用遵循依赖倒置原则的工厂方法模式重构命令类代码?

问题:重构命令类以遵循依赖倒置原则

我编写了如下命令类的handle方法代码,希望对其进行重构以遵循依赖倒置原则(DI):

public function handle()
{
    $productChoice = $this->argument('product');

    if ($productChoice == 1) {
        $product = new ProductA();
    } elseif ($productChoice == 2) {
        $product = new ProductB();
    } else {
        $this->error('Invalid product choice. Use 1 for ProductA or 2 for ProductB.');
        return;
    }

    if ($product instanceof ProductI) {
        $this->info($product->details());
    } else {
        $this->error('The selected product does not implement the ProductI interface.');
    }
}

如你所见,该命令类直接依赖ProductA和ProductB具体类。我的问题是如何对其进行重构?

我有以下两种思路,但不确定哪种更优或是否存在更好的方案:

思路1:创建工厂类并注入到命令类

class ProductFactory
{
    public function createProduct($productChoice)
    {
        switch ($productChoice) {
            case 1:
                return new ProductA();
            case 2:
                return new ProductB();
            default:
                return null;
        }
    }
}

思路2:创建服务提供者,通过容器解析

public function register()
{
    $this->app->bind('Product1', function () {
        return new ProductA();
    });

    $this->app->bind('Product2', function () {
        return new ProductB();
    });
}

随后在命令类中通过$product = $this->app->make("Product{$productChoice}")获取实例。


回答

两种方案都能解决命令类直接依赖具体产品类的问题,核心是将实例化逻辑从命令类剥离,符合依赖倒置原则(依赖抽象而非具体实现),但适用场景各有侧重:

方案1:工厂类

  • 优势:逻辑集中直观,专门负责产品实例创建,命令类只需依赖工厂抽象(若给工厂定义接口则更严谨),后续新增产品仅需修改工厂方法,符合单一职责原则。
  • 优化建议:
    1. 给工厂类定义ProductFactoryInterface,让命令类依赖接口而非具体工厂,后续替换工厂实现更灵活;
    2. 不要返回null,改为抛出自定义异常,让命令类更优雅地处理无效选择。

优化后的工厂类及命令类示例:

// 定义工厂接口
interface ProductFactoryInterface
{
    public function createProduct(int $productChoice): ProductI;
}

// 实现工厂类
class ProductFactory implements ProductFactoryInterface
{
    public function createProduct(int $productChoice): ProductI
    {
        return match($productChoice) {
            1 => new ProductA(),
            2 => new ProductB(),
            default => throw new InvalidProductChoiceException('Invalid product choice. Use 1 for ProductA or 2 for ProductB.')
        };
    }
}

// 重构后的命令类
class YourCommand extends Command
{
    private ProductFactoryInterface $factory;

    // 通过构造函数注入工厂
    public function __construct(ProductFactoryInterface $factory)
    {
        parent::__construct();
        $this->factory = $factory;
    }

    public function handle()
    {
        try {
            $product = $this->factory->createProduct($this->argument('product'));
            $this->info($product->details());
        } catch (InvalidProductChoiceException $e) {
            $this->error($e->getMessage());
        }
    }
}

方案2:服务提供者+容器解析

  • 优势:利用框架容器机制,无需额外依赖工厂类,直接通过容器获取实例;若产品类本身有依赖,容器可自动注入,无需手动处理。
  • 优化建议:
    1. 将产品选择与容器标识的映射抽离为配置数组,避免硬编码字符串,降低出错概率;
    2. 新增产品时只需修改配置,无需改动服务提供者逻辑。

优化后的服务提供者及命令类示例:

// config/products.php 配置文件
return [
    1 => ProductA::class,
    2 => ProductB::class,
];

// 服务提供者
class ProductServiceProvider extends ServiceProvider
{
    public function register()
    {
        $products = config('products');
        foreach ($products as $choice => $class) {
            $this->app->bind("Product{$choice}", function () use ($class) {
                return $this->app->make($class);
            });
        }
    }
}

// 重构后的命令类
class YourCommand extends Command
{
    public function handle()
    {
        $choice = $this->argument('product');
        $containerKey = "Product{$choice}";
        
        if (!$this->app->bound($containerKey)) {
            $this->error('Invalid product choice. Use 1 for ProductA or 2 for ProductB.');
            return;
        }
        
        $product = $this->app->make($containerKey);
        if ($product instanceof ProductI) {
            $this->info($product->details());
        } else {
            $this->error('The selected product does not implement the ProductI interface.');
        }
    }
}

方案选择建议

  • 若产品创建逻辑复杂(需依赖其他服务、初始化参数),或后续可能有多种创建策略,优先选工厂类方案,封装性与扩展性更强;
  • 若项目重度依赖框架容器,产品创建逻辑简单,选服务提供者方案更贴合框架生态。

此外,还可以结合两种方案的优势:让工厂类依赖容器来创建产品实例,既保留工厂的集中逻辑,又能利用容器的自动注入能力。


内容的提问来源于stack exchange,提问作者Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 19:32:04