php中封装本质是定义契约边界而非数据隐藏,组合优于继承因它避免子类对父类实现的强依赖;正确做法是用private限定内部细节、通过构造注入协作对象,并仅在抽象模板或final稳定类时才安全使用继承。

PHP里封装和“组合优于继承”不是两个并列概念,而是不同层级的设计选择:封装是语法强制的访问控制手段,组合是一种架构决策;用错封装会立刻报错,而滥用继承可能半年后才暴露维护灾难。
PHP封装的本质是访问控制,不是数据隐藏
很多人以为private就是“把东西藏起来”,其实PHP封装的核心作用是**定义契约边界**:哪些成员允许被外部或子类依赖,哪些只属于内部实现细节。一旦你用protected暴露一个属性,子类就隐式地与该属性的结构、生命周期和语义强绑定——这恰恰是组合要避免的。
-
public属性或方法 = 公开接口,改了就得通知所有调用方 -
protected成员 = 子类可依赖,但父类重构时必须同步考虑所有子类行为 -
private才是真正的封装底线:它不参与继承链,也不向子类承诺任何东西,后续可安全重命名、拆分或删除
常见错误是把本该private的配置项写成protected,结果子类偷偷读取并依赖它的格式(比如$this->config['timeout']),等你某天改成TimeoutConfig对象时,整个继承树就崩了。
组合在PHP中落地就是“用对象代替继承”
PHP不支持多继承,但你可以让一个类has-a多个协作对象,而不是is-a某个庞大基类。关键不是“不用extends”,而是**把可变点抽成独立类,通过构造函数或setter注入**。
- 不要写
class ReportPDF extends ReportBase,而是class Report { private $renderer; private $exporter; } - 子类爆炸场景(如
AdminUser、GuestUser、APIUser)更适合用User类持有一个RoleStrategy对象,而不是层层继承 - PHP的
__construct()参数和类型声明(如function __construct(RendererInterface $r))天然支持组合,比extends更易测试、替换和文档化
注意:组合不等于“到处new对象”。如果new FileLogger()散落在10个类里,那只是把耦合从继承挪到了实例化位置——得配合依赖注入容器或工厂来管理生命周期。
继承在PHP中真正安全的用法只有两种
PHP的单继承+无final默认机制,让extends极易失控。除非满足以下任一条件,否则优先选组合:
- 父类是
abstract且只定义模板方法(如process()调用doParse()和doSave()),子类只实现钩子,不覆盖核心流程 - 父类是
final,且明确标注为“不可继承的稳定契约”(比如DateTimeImmutable)
反例:class BaseController extends FrameworkController——这种继承链一旦框架升级,你的BaseController就可能因父类方法签名变更而静默失效。而组合方案(class BaseController { private $frameworkAdapter; })只要接口没变,内部换Laravel或Symfony都无关痛痒。
最常被忽略的点是:PHP的private方法无法被子类覆盖,但它仍会出现在get_class_methods()结果里;而组合中的委托对象,连反射都看不到——这才是真正的解耦。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











