如何使用遵循依赖倒置原则的工厂方法模式重构命令类代码?
问题:重构命令类以遵循依赖倒置原则
我编写了如下命令类的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:工厂类
- 优势:逻辑集中直观,专门负责产品实例创建,命令类只需依赖工厂抽象(若给工厂定义接口则更严谨),后续新增产品仅需修改工厂方法,符合单一职责原则。
- 优化建议:
- 给工厂类定义
ProductFactoryInterface,让命令类依赖接口而非具体工厂,后续替换工厂实现更灵活; - 不要返回
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:服务提供者+容器解析
- 优势:利用框架容器机制,无需额外依赖工厂类,直接通过容器获取实例;若产品类本身有依赖,容器可自动注入,无需手动处理。
- 优化建议:
- 将产品选择与容器标识的映射抽离为配置数组,避免硬编码字符串,降低出错概率;
- 新增产品时只需修改配置,无需改动服务提供者逻辑。
优化后的服务提供者及命令类示例:
// 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
相关产品推荐
相关产品推荐

