基于SOLID原则的PHP购物车产品数量控制实现是否符合抽象原则?
首先得说,你的核心思路是对的——用抽象类+多态来区分数字产品和实体产品的数量控制逻辑,这已经摸到了抽象和多态的门儿,咱们再一步步拆解下符合和可以优化的地方:
做得好的地方(符合原则的部分)
- 依赖倒置原则:你用抽象类
Products定义了产品的核心契约(isDigital方法),高层逻辑(比如购物车的数量控制)只需要依赖这个抽象,而不是具体的DigitalProduct或UnDigitalProduct,这部分是符合SOLID的。 - 单一职责(子类层面):每个子类只负责实现自己的
isDigital判定,没有额外的冗余逻辑,职责清晰。
可以优化的点(不符合原则的部分)
1. 违反里氏替换原则(LSP)
DigitalProduct继承了Products,但它的setQuantity方法是无效的——调用后不会有任何效果,却没有抛出提示或报错。里氏替换原则要求子类可以完全替换父类的位置,而不会让调用方产生意外行为。如果有其他代码拿着Products类型的变量调用setQuantity,预期是数量被修改,但数字产品却没变化,这很容易埋下隐性bug。
2. 违反开闭原则(OCP)
父类Products里的setQuantity方法依赖isDigital的条件判断,如果以后新增一种特殊产品(比如「带电子激活码的实体硬件」,既需要数量又有数字属性),你就得修改父类的条件逻辑,这就违反了「对扩展开放,对修改关闭」的原则。
3. 父类职责不单一(违反单一职责原则)
Products类同时承担了「通用产品属性管理」和「数字产品数量校验」两个职责,把数量控制的逻辑硬塞在了父类里,导致父类的职责不够纯粹。
4. 抽象粒度不够精准(不符合抽象原则)
抽象原则要求抽象类只包含所有子类共有的行为和属性,但setQuantity并不是数字产品需要的方法,把它放在父类里,相当于给数字产品强加了一个它不需要的能力,反而让抽象变得冗余。
优化思路与示例
方案一:用接口拆分不同产品类型的契约
既然数字产品和实体产品的核心行为差异很大(一个不需要设置数量,一个需要),不如直接用接口来定义不同类型的能力:
<?php // 数字产品的接口,只定义数字产品专属的行为 interface DigitalProduct { public function getDownloadUrl(): string; // 其他数字产品方法... } // 实体产品的接口,包含数量控制能力 interface PhysicalProduct { public function setQuantity(int $quantity): void; public function getQuantity(): int; // 其他实体产品方法... } // 具体数字产品实现 class EBook implements DigitalProduct { public function getDownloadUrl(): string { return "https://example.com/ebook/download"; } } // 具体实体产品实现 class TShirt implements PhysicalProduct { private int $quantity = 1; public function setQuantity(int $quantity): void { $this->quantity = $quantity; } public function getQuantity(): int { return $this->quantity; } }
这样一来,调用方在处理产品时,很清楚只有PhysicalProduct类型的产品才能设置数量,DigitalProduct根本没有这个方法,完全避免了无效调用的问题,也完美符合里氏替换和开闭原则——新增产品类型只需要实现对应接口,不用修改现有代码。
方案二:精简抽象类,让子类自主实现专属行为
如果想保留基础的产品抽象类(用来统一管理id、名称等通用属性),可以把数量控制逻辑完全移到实体产品子类中,父类只保留所有产品共有的内容:
<?php abstract class Product { protected string $id; protected string $name; public function __construct(string $id, string $name) { $this->id = $id; $this->name = $name; } // 通用方法,所有产品都需要 public function getName(): string { return $this->name; } public function getId(): string { return $this->id; } } class DigitalProduct extends Product { // 数字产品的专属方法,比如获取激活码 public function getActivationCode(): string { return uniqid("ACT-"); } } class PhysicalProduct extends Product { private int $quantity = 1; public function setQuantity(int $quantity): void { $this->quantity = $quantity; } public function getQuantity(): int { return $this->quantity; } }
这个方案里,抽象类只负责通用属性和方法,子类各自实现自己的专属能力,没有冗余的无效方法,代码更干净,也符合所有SOLID原则。
总结
你的核心方向是对的,但通过调整抽象的粒度和职责划分,可以让代码更健壮、更易扩展。优化后的方案能避免隐性bug,也能让后续新增产品类型的成本更低。
内容的提问来源于stack exchange,提问作者akio

