在PHP面向对象开发中,public、private和protected看起来只是三个简单的关键字,却直接决定了一个类的内部实现究竟向外暴露多少。
很多初学者会把它们简单记成“public谁都能访问,protected子类能访问,private只有自己能访问”。这个记忆方法没有错,但如果真正进入大型项目开发,仅仅记住这三个定义远远不够。
访问控制解决的核心问题,是一个类应该向外部公开什么,以及哪些实现细节应该留在类的内部。它直接影响代码的封装、继承、维护和后期修改成本。
public到底意味着什么
public是开放程度最高的访问级别。
一个类的public属性或者方法,可以在类内部使用,也可以从类外部直接访问,继承它的子类同样可以访问。
例如一个User类提供getName()方法,如果这个方法是public,那么其他代码可以通过User对象调用它。这个方法就相当于这个类公开给其他程序使用的一部分接口。
问题在于,public并不只意味着“方便使用”,同时也意味着这个成员成为了外部代码可以依赖的一部分。
如果把对象内部的重要状态全部设置为public,其他代码就可能直接修改这些数据。
例如用户年龄、账户余额、订单状态等属性,如果允许外部代码随意赋值,类本身就很难保证这些数据始终符合业务规则。
所以在实际项目中,public通常更适合用于那些本来就希望被外部调用的操作,而不是把所有内部数据直接暴露出来。
private保护的是类自己的实现
private是限制最严格的访问级别。
private成员只能在定义它的那个类内部访问。类外部不能直接访问,子类也不能直接访问父类的private成员。
这最后一点非常重要。
假设父类User有一个private属性$passwordHash,即使AdminUser继承User,AdminUser也不能直接使用$passwordHash这个父类私有属性。
这并不意味着数据不存在,而是意味着父类决定不把这个内部状态作为继承体系的一部分直接暴露给子类。
如果父类需要让子类执行某种操作,可以通过protected或public方法提供受控接口。
private的价值就在这里:它允许一个类隐藏自己的内部实现。
例如订单类内部可能有计算价格、检查库存、生成订单编号等多个步骤。外部代码并不需要知道这些步骤具体怎么实现,只需要调用一个公开的创建订单方法。
以后开发者改变内部算法,只要公开接口没有改变,调用这个类的其他代码通常不需要跟着修改。
这就是封装带来的维护优势。
protected为什么存在
protected处于public和private之间。
protected成员可以在定义它的类内部访问,也可以在继承它的子类内部访问,但不能从普通的类外部直接访问。
它主要解决的是一个问题:父类希望把某些实现能力提供给继承体系中的子类,但又不希望把这些成员公开给所有外部代码。
例如一个基础日志类可能提供一个protected方法,让不同的日志子类按照自己的方式写入日志。
这样,外部程序不能直接调用这个内部实现,但子类仍然可以利用父类提供的扩展点。
不过,protected并不意味着“安全”。
如果一个属性是protected,子类通常可以直接读取和修改它。因此,使用protected实际上扩大了继承体系对父类内部状态的访问范围。
如果父类并不希望子类直接修改某个内部状态,就不能因为“反正外部访问不到”而简单使用protected。
三者最重要的区别其实是继承
从外部代码来看,public、protected和private的区别比较容易理解。
真正容易出错的地方在继承。
父类的public成员会继续作为公开成员被子类继承使用。
父类的protected成员对子类仍然可见,因此子类可以直接使用。
父类的private成员则属于父类自己的内部实现,子类不会直接获得对它的访问权。
这也是为什么在设计父类时,不能把protected当成“默认比private方便一点”。
一旦一个属性或者方法被设计成protected,实际上就是在告诉未来的子类:“这个成员属于继承体系可以依赖的内部接口。”
如果未来有多个子类依赖这个成员,父类开发者再想修改它,就可能影响整个继承体系。
因此,protected虽然比public封闭,但它仍然会产生较强的继承耦合。
private属性不代表子类完全无法使用相关数据
假设父类有一个private属性$email,子类不能直接写:
$this->email
但父类可以提供一个public或者protected方法,例如getEmail(),让子类通过这个方法获得需要的信息。
这种方式的意义在于,子类依赖的是父类提供的行为,而不是直接依赖父类内部的数据结构。
这两种设计的维护成本可能完全不同。
如果子类直接依赖父类的protected属性,父类以后改变属性名称、存储方式或者数据结构,所有子类都可能需要修改。
如果子类只是调用父类提供的方法,父类内部如何保存数据就可以发生变化。
这也是现代面向对象设计中经常强调“面向接口和行为,而不是直接依赖内部状态”的原因之一。
getter和setter并不是越多越好
很多PHP项目会采用private属性,然后给每个属性提供一个getter和setter。
这种做法有时有用,但不能把它理解成一种固定模板。
如果一个属性设置成private,然后马上提供完全没有逻辑的getter和setter,例如:
getAge()
和
setAge($age)
实际上只是把public属性换了一个写法。
真正有价值的setter应该能够在数据进入对象内部时执行必要的业务约束。
例如年龄不能是负数,订单状态不能从“已完成”直接修改成“待付款”,账户余额不能通过普通setter任意设置。
如果对象内部存在这些业务规则,那么由类自身控制状态变化就有意义。
因此,封装的目标并不是“所有属性都private,然后全部生成getter和setter”,而是让对象自己负责维护自己的状态。
public方法通常比public属性更适合作为接口
假设一个BankAccount类有账户余额。
如果余额是public,外部代码可以直接修改:
$account->balance = -100000;
类本身很难阻止这种行为。
如果余额是private,那么外部代码不能直接改变它。类可以提供一个public的deposit()或者withdraw()方法,并在这些方法中执行金额、余额和业务状态检查。
这时候类不仅保存数据,还负责定义数据应该怎样变化。
这才是面向对象封装的重要意义。
所以在设计类的时候,通常应该优先考虑“外部代码需要让这个对象做什么”,而不是“外部代码需要直接修改对象里的哪些变量”。
private还有一个容易忽略的特点
父类和子类可以分别拥有同名的private属性,因为它们属于不同的类内部状态。
这意味着继承关系中的private成员并不是简单的“子类看不到父类变量”。
父类的private成员仍然由父类自己的方法管理,而子类可以拥有自己的同名private成员,两者并不是同一个可以直接互相访问的属性。
这也是为什么复杂继承体系中如果大量依赖private状态,需要认真设计父类提供给子类的接口,否则很容易让代码逻辑变得难以理解。
方法的访问级别也影响继承
访问控制不仅适用于属性,也适用于方法。
如果一个方法是public,外部代码可以调用它,因此它通常属于类对外公开的接口。
protected方法主要服务于继承体系。
private方法则属于类自己的内部实现,外部代码和子类都不能直接调用。
因此,一个复杂类完全可以存在三层方法:public方法负责对外提供业务能力,protected方法负责给子类提供扩展点,private方法负责内部实现细节。
这样的结构比把所有方法都public更加容易维护。
同时,子类覆盖父类方法时,还需要遵守PHP关于方法可见性的规则。通常不能把一个父类已经公开的方法在子类中降低访问级别,否则会破坏继承关系所要求的可替换性。
protected和private应该怎么选择
如果某个成员完全属于当前类的内部实现,而且未来没有必要让子类直接访问,private通常更加合适。
如果这个成员明确属于一个需要被子类扩展的设计点,可以考虑protected。
但如果系统根本不需要继承,那么protected往往没有必要。
现代PHP项目中,如果一个类只是为了复用代码而建立复杂继承体系,也应该谨慎考虑。很多情况下,通过组合、依赖注入和接口来组织代码,比建立深层次的父子类关系更容易维护。
因此,protected并不是“高级开发者应该多使用的权限”,而是一种有明确使用场景的继承机制。
访问控制和系统安全不是一回事
还需要特别区分“代码封装”和“安全”。
把数据库密码放进private属性,并不意味着数据库密码因此获得了完整的安全保护。PHP访问修饰符主要解决的是对象内部的代码访问边界,而不是操作系统、数据库、服务器或者密钥管理层面的安全问题。
同样,把用户数据设置成private,也不意味着网站API就自动安全。
真正的应用安全还需要身份认证、授权、输入验证、SQL参数化、会话保护、密钥管理以及日志审计等多个层面共同完成。
访问控制的主要价值,是限制程序内部不同代码之间的依赖关系,降低错误修改和意外耦合。
在实际PHP项目中应该怎么设计
如果一个类需要向外部程序提供功能,就把必要的方法设计成public。
如果某个状态只应该由这个类自己维护,可以优先考虑private。
如果存在明确的继承关系,而且子类确实需要访问某个成员,再考虑protected。
对于重要业务状态,不要因为“方便”就全部设置为public。应该让类通过明确的方法控制状态变化,把验证规则放在合适的位置。
与此同时,也不要为了追求所谓的“高封装”而把所有东西都设计成private,然后制造大量没有实际意义的getter和setter。
真正好的访问控制不是把代码全部锁起来,而是明确边界:哪些是稳定的公开接口,哪些是继承体系需要使用的扩展能力,哪些只是当前类自己的实现细节。
public解决的是“哪些能力需要对外提供”,protected解决的是“哪些能力需要在继承体系中共享”,private解决的是“哪些实现应该留在当前类内部”。
当一个PHP项目规模扩大以后,这种区别会直接影响代码的修改成本。一个类如果把大量内部状态暴露成public,任何地方都可能依赖它;如果大量使用protected,父类和子类之间又会形成紧密耦合;如果合理使用private并通过清晰的方法提供必要能力,类的内部实现就可以在不影响外部调用者的情况下不断调整。
所以,学习这三个关键字并不只是记住三个定义。它实际上是在学习面向对象设计中一个非常基础的原则:代码应该明确知道自己能够依赖什么,也应该明确哪些内部细节不应该被其他代码随意依赖。访问控制设计得越清楚,系统到了后期越容易修改,而不会因为一个看似简单的属性变化,牵动整个项目。
PHP public、private 和 protected 怎么区分?面向对象开发中的访问控制
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
分享 Facebook | X | WhatsApp | LinkedIn
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP