优先选组合而非继承的核心判断是:当类间非“is-a”而是“has-a”或“uses-a”关系时,如user需日志能力而非“是日志器”,此时继承会导致职责混乱、扩展僵化、违反单一职责,而组合支持运行时替换、解耦依赖、适配php单继承限制。

该用组合而不是继承的判断,核心就一条:当两个类之间不是“is-a”而是“has-a”或“uses-a”关系时,优先选组合。
遇到“is-a”不成立时立刻停手
比如你想让 User 类复用日志能力,写成 class User extends Logger 就错了——用户不是日志器。这种强行继承会导致子类承担无关职责,后续修改一处逻辑可能意外影响另一处。
- 典型错误现象:
User类里突然多了writeToFile()、rotateLog()这类和业务无关的方法 - 真实场景:支付网关需要通知能力、订单需要缓存策略、用户需要权限校验——这些都不是“是某种东西”,而是“用某种东西”
- 正确做法:把日志、缓存、通知各自封装成独立类,
User通过构造函数或 setter 注入它们
需要运行时切换行为时必须用组合
继承在编译时就绑死了父类行为,无法动态替换。比如一个 PaymentProcessor 类,上线后要支持支付宝、微信、PayPal 多种渠道,且可能按地区或用户等级实时切换——这时继承 AlipayProcessor 或 WechatProcessor 都会卡死扩展路径。
- 常见错误:为每种支付方式建一个子类,然后用 if-else 切换实例类型
- 参数差异:
AlipayProcessor和PayPalProcessor可能有完全不同的配置项(如app_idvsclient_id),继承结构难以统一构造签名 - 性能影响:继承树过深会增加对象创建开销;组合则只实例化当前需要的依赖,内存更可控
PHP 特性限制让组合更自然
PHP 是单继承语言,但一个类往往需要多个横向能力(日志、缓存、验证、重试)。如果全靠继承,很快就会撞上“只能 extend 一个类”的天花板。
- Trait 确实缓解了部分问题,但它解决的是“方法复用”,不是“职责解耦”。Trait 不能替代组合来管理对象生命周期或依赖关系
- 容易踩的坑:把
use Cacheable, Loggable, Validatable塞进一个大类里,结果这个类既管数据又管日志还管缓存,违背单一职责 - 兼容性提示:PHP 8.2+ 对只读属性、构造函数可见性等约束更严,组合方式比继承更容易适配这些变化
最常被忽略的一点:组合不是“不用继承”,而是“把继承留给真正表达 is-a 的地方”。比如 AdminUser 继承 User 合理,但 User 再去继承 DatabaseRecord 就越界了——数据库操作是它用的能力,不是它的身份。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











