您现在的位置是:首页 >其他 >闪耀的钥匙:PHP 与访问修饰符网站首页其他
闪耀的钥匙:PHP 与访问修饰符
文章目录
参考
项目 | 描述 |
---|---|
搜索引擎 | Google 、Bing |
PHP | 官方文档 |
描述
项目 | 描述 |
---|---|
PHP | php-8.2.6-Win32-vs16-x64 |
PhpStorm | PhpStorm 2023.1.1(Profession) |
访问修饰符
访问修饰符
访问修饰符在面向对象编程中起着重要的作用,它们提供了 对类的属性和方法的访问级别控制
。访问修饰符能够为你的程序带来以下优点:
-
封装性(Encapsulation)
访问修饰符允许将类的内部实现细节隐藏起来,仅暴露必要的公有接口给外部使用
。这样可以有效地封装数据和行为,提高代码的可维护性和可重用性。 -
访问控制(Access Control)
通过访问修饰符,可以控制类的成员对外部的可见性和可访问性
。不同的访问修饰符可以限制属性和方法的访问范围,只允许特定的代码段访问,从而提供了更好的安全性和数据保护。
PHP 与访问修饰符
PHP 支持三种访问修饰符:public
,protected
以及 private
,这些修饰符所提供的访问级别具体如下。
修饰符 (Modifier) | 访问级别 (Access Level) | 类内部 (Inside Class) | 子类 (In Subclass) | 外部 (Outside Class) |
---|---|---|---|---|
public | 公有 | 可访问 | 可访问 | 可访问 |
protected | 受保护 | 可访问 | 可访问 | 不可访问 |
private | 私有 | 可访问 | 不可访问 | 不可访问 |
注:
在 PHP 中,访问修饰符是可以省略的,但这仅仅针对于类中的方法而言。默认情况下,如果没有指定访问修饰符,方法都将被视为公有成员。
public
公有成员(使用 public 作为访问修饰符的方法或属性)在 PHP 中具有全局可见性,可以从类的内部和外部进行访问。
举个栗子
<?php
# 定义一个类,该类的所有属性与方法均为
# 公有成员。
class MyClass
{
public $species = 'Human';
public $hobby = 'Fantasy';
public $secret = 'Secret';
# 方法可以省略访问修饰符,
# 默认使用 public 访问修饰符。
function petPhrase()
{
echo 'Ni Ma';
}
function spy()
{
# 在类的内部访问公有成员
echo $this -> secret;
}
}
# 定义一个类,该类继承自 MyClass
# 属于 MyClass 的子类。
class Agent extends MyClass
{
# 通过子类访问父类 MyClass 中的公有成员。
function domesticThief()
{
echo $this -> secret;
}
}
# 实例化 MyClass
$myClass = new MyClass();
# 在类的外部访问 MyClass 类中的公有成员
echo $myClass -> species;
echo '</br>';
echo $myClass -> hobby;
echo '</br>';
$myClass -> petPhrase();
echo '</br>';
$myClass -> spy();
echo '</br>';
# 实例化 Agent
$agent = new Agent();
# 通过访问 $agent 中的 domesticThief 方法
# 间接访问 MyClass 中的公有成员 secret。
$agent -> domesticThief();
?>
上述代码定义了两个类:MyClass
和 Agent
。
-
MyClass
类是一个基类,其中包含了公有属性species
、hobby
和secret
,以及两个方法petPhrase()
和spy()
。MyClass
类中的方法没有显式指定访问修饰符,默认使用public
访问修饰符。 -
Agent
类是MyClass
类的子类,通过使用extends
关键字继承自MyClass
。子类可以继承父类的属性和方法。 -
Agent
类中定义了一个方法domesticThief()
,该方法尝试直接访问父类MyClass
的公有属性secret
。
在代码的后半部分,通过实例化对象并调用相应的方法来演示 public
访问修饰符所提供的访问权限:
-
首先,创建一个
MyClass
类的实例$myClass
,通过$myClass->属性名
和$myClass->方法名()
的方式在类的外部访问和调用该类的公有属性和方法,并通过spy()
方法由类的内部对MyClass
类的secret
属性进行访问。 -
接着,创建一个
Agent
类的实例$agent
,调用domesticThief()
方法。该方法尝试直接访问父类MyClass
的secret
属性。由于secret
属性在父类中是公有的,因此子类可以访问该属性,并输出了结果。
执行效果
Human
Fantasy
Ni Ma
Secret
Secret
protected
受保护成员(使用 protected
作为访问修饰符的属性或方法)只能在定义它们的类内部或该类的子类中访问,外部无法直接访问受保护成员。
举个栗子
<?php
class MyClass
{
public $species = "Human";
public $hobby = "Fantasy";
# 定义受保护成员属性
protected $secret = "Secret";
# 定义受保护成员方法
protected function petPhrase()
{
echo 'Ni Ma';
}
function spy()
{
# 在类的内部访问受保护成员
echo $this -> secret;
}
}
class Agent extends MyClass
{
# 通过子类访问父类 MyClass 中的受保护成员属性
function domesticThief()
{
echo $this -> secret;
}
}
$myClass = new MyClass();
# 在类的外部类访问 MyClass 中的公有成员
echo $myClass -> species;
echo '</br>';
echo $myClass -> hobby;
echo '</br>';
$myClass -> spy();
echo '</br>';
# 不可使用如下语句在外部访问 MyClass 中的私有成员属性
# 否则,PHP 将抛出错误信息。
// echo $myClass -> secret;
// $myClass -> petPhrase();
$agent = new Agent();
# 通过访问 $agent 中的 domesticThief 方法
# 间接访问 MyClass 中的私有成员 secret。
$agent -> domesticThief();
其中:
-
MyClass
类中的$species
和$hobby
属性被声明为公有(public
),因此可以从类的内部和外部访问。 -
MyClass
类中的$secret
属性被声明为受保护(protected
),这意味着它只能在定义它们的类内部和子类中访问。在类的内部,spy()
方法通过$this->secret
访问了受保护属性。 -
MyClass
类中的petPhrase()
方法也被声明为受保护(protected
),只能在定义它们的类的内部和子类中访问。 -
Agent
类继承自MyClass
类,因此可以访问父类中的受保护成员属性和方法。 -
Agent
类中的domesticThief()
方法尝试直接访问父类MyClass
的受保护属性$secret
。由于受保护属性在子类中可见,因此可以输出结果。 -
尝试在外部访问
MyClass
类的私有成员属性$secret
和私有成员方法petPhrase()
,会导致 PHP 抛出错误信息。
执行效果
Human
Fantasy
Secret
Secret
private
私有属性和方法只能在定义它们的类内部访问
,它们对于类的外部及其子类来说是不可见的。
举个栗子
<?php
class MyClass
{
public $species = "Human";
public $hobby = "Fantasy";
# 定义私有成员
private $secret = "Secret";
# 定义私有成员方法
private function petPhrase()
{
# 在类的内部访问私有成员变量
echo $this -> secret;
}
function spy()
{
# 在类的内部访问私有成员方法
$this -> petPhrase();
}
}
$myClass = new MyClass();
# 在类的外部访问 MyClass 中的公有成员
echo $myClass -> species;
echo '</br>';
echo $myClass -> hobby;
echo '</br>';
# 通过在类的外部访问 MyClass 中的公有成员
# 间接访问 MyClass 中的私有成员。
$myClass -> spy();
# 私有成员变量无法从定义它们的类的外部
# 及该类的子类中直接进行访问。否则,
# PHP 将抛出异常错误。
?>
其中:
MyClass
类中的$species
和$hobby
属性被声明为公有(public
),因此可以从类的内部和外部访问。MyClass
类中的$secret
属性被声明为私有(private
),这意味着它只能在定义它们的类内部访问。在类的内部,spy()
方法通过调用私有方法petPhrase()
来间接访问私有属性。MyClass
类中的petPhrase()
方法也被声明为私有(private
),只能在定义它们的类内部访问。
执行效果
Human
Fantasy
Secret
继承中的重写规则
在 PHP 中,访问修饰符和继承之间有一些重写规则,它们决定了子类如何访问和重写父类的属性和方法。
子类对父类成员的访问权限和重写能力
访问修饰符 | 继承中的访问权限 | 重写规则 |
---|---|---|
public | 可以被继承 | 可以直接访问和重写。在子类中与父类具有相同的访问修饰符,或可以扩大为更宽松的修饰符。 |
protected | 可以被继承 | 可以直接访问和重写。在子类中与父类具有相同的访问修饰符,或可以扩大为更宽松的修饰符。 |
private | 不能被继承 | 无法直接访问和重写。子类可以定义具有相同名称的私有成员,但它们只是在子类中重新声明了一个新的私有成员,与父类中的私有成员没有任何关联。 |
可见性
在重写一个父类方法或属性时,子类的成员的可见性不能低于父类相同成员的可见性。可见性按照以下顺序从高到低:private
> protected
> public
。这意味着子类成员可以将父类成员的可见性扩大,但不能缩小。
- 例如,如果父类方法是
protected
,子类方法可以是protected
或public
,但不能是private
。 - 如果父类方法是
public
,子类方法也可以是protected
或public
,但不能是private
。
举个栗子
未重写父类的受保护成员前
<?php
class Boss
{
protected function target()
{
echo 'Boss -> target()';
}
}
class Boy extends Boss
{
}
$boy = new Boy();
# 由于 target() 方法在父类中为
# 受保护成员,因此执行下述语句
# PHP 将抛出错误(在类的外部对其进行访问)。
$boy -> target();
?>
将受保护成员重写为公有成员
<?php
class Boss
{
protected function target()
{
echo 'Boss -> target()';
}
}
class Boy extends Boss
{
public function target()
{
echo 'Boy -> target()';
}
}
$boy = new Boy();
$boy -> target();
?>
执行效果
Child -> target()
注:
重写父类的方法只会影响到继承该类并重写了该方法的子类对象,其他继承该类的对象不会受到影响。
当子类重写父类的方法时,它实际上是在子类中创建了一个与父类方法具有相同 签名(函数的签名指的是函数的名称以及其参数列表的组合,它定义了函数的唯一标识符,用于区分不同的函数)
的新方法。这个新方法将覆盖(或取代)父类中的方法。当通过子类对象调用该方法时,将执行子类中的方法而不是父类中的方法。
其他继承了父类但没有重写该方法的子类对象仍然会继续使用父类中的方法,重写只会影响到具体进行重写操作的子类及其实例对象
。
将受保护成员重写为私有成员
<?php
class Boss
{
protected function target()
{
echo 'Boss -> target()';
}
}
class Boy extends Boss
{
private function target()
{
echo 'Boy -> target()';
}
}
$boy = new Boy();
$boy -> target();
?>
执行效果
在 PHP 中,当子类重写父类的方法时,重写的方法的访问修饰符不能比父类中的方法更严格。在上述代码中,父类的 target()
方法被定义为 protected
访问修饰符,而子类中重写的 target()
方法被定义为 private
访问修饰符,PHP 由此抛出了异常信息。
Fatal error: Access level to Boy::target() must be protected (as in class Boss) or weaker in C:WWWindex.php on line 21
为什么不允许子类成员设置比父类成员更严格的访问限制?
在继承过程中,子类中的方法的访问修饰符不允许设置比父类更严格,主要是出于以下几个原因:
-
继承的一致性
继承是一种行为,子类继承了父类的属性和方法,并且可以通过重写方法来改变其行为。如果子类中的方法访问修饰符设置得比父类更严格,那么就破坏了继承的一致性,导致子类无法完全替代父类的功能
。 -
多态性的保证
继承关系中的多态性是面向对象编程的重要特性之一。子类可以以父类的形式存在,而父类中的方法可以被子类重写。如果子类中的方法访问修饰符比父类更严格,那么无法保证子类以父类的形式出现时,能够正确地调用和执行相应的方法
。
当一个方法在父类中被声明为公有(public),意味着该方法对外部是可见的,其他类可以自由地访问和使用它。如果子类中将该方法的访问修饰符设置为私有(private)或受保护(protected),则违反了父类对外提供的访问权限,导致无法在子类或其他类中正确地访问该方法。
继承关系中,子类应该是父类的扩展,它应该继承并保留父类的接口和行为
。子类的方法的访问修饰符应该不严于父类,以确保子类能够正常地访问和重写父类的方法。这样可以保持代码的一致性,遵循面向对象编程的原则,提高代码的可读性、可维护性和可扩展性。
因此,在继承过程中,不允许子类中的方法的访问修饰符设置比父类更严格,以保证继承关系的正确性和代码的稳定性。
final 关键字
final
关键字在 PHP 中用于限制类、方法或属性的继承和重写。它的主要功能如下:
作用对象 | 功能和作用 |
---|---|
类 | 防止其他类继承,保证实现的安全性和稳定性 |
方法 | 防止子类重写父类方法,保持方法行为的一致性和可预测性 |
属性 | 属性不能声明为 final,只有类和方法可以。属性的可见性可以通过访问修饰符来控制,但无法使用 final 限制属性的继承或修改。 |
使用 final
关键字可以在类的设计中提供一定的约束和保护机制。它能够防止意外的修改、继承或重写,从而增加代码的可维护性和可靠性。需要注意的是,final
关键字应该谨慎使用,只在必要的情况下才使用,以免过度限制类的扩展和修改。
类
当一个类被声明为 final
,它将不能被其他类继承。这样可以确保该类的实现不会被修改或扩展,提供了一定的安全性和稳定性。
举个栗子
<?php
final class Boss
{
function target()
{
echo 'Boss -> target()';
}
}
class Boy extends Boss
{
}
?>
执行效果
Fatal error: Class Boy may not inherit from final class (Boss) in C:WWWindex.php on line 14
方法
当一个方法被声明为 final
,它将不能被子类重写。这样可以确保该方法的行为不会被修改,保持一致性和可预测性。子类无法改变父类中标记为 final
的方法的实现。
举个栗子
<?php
class Boss
{
final protected function target()
{
echo 'Boss -> target()';
}
}
class Boy extends Boss
{
public function target()
{
echo 'Boy -> target';
}
}
?>
执行效果
Fatal error: Cannot override final method Boss::target() in C:WWWindex.php on line 17
属性
在 PHP 中,属性不能被声明为 final
,只有类和方法可以。属性的可见性可以通过访问修饰符来控制,但无法使用 final
限制属性的继承或修改。
举个栗子
<?php
class Boss
{
final protected $target = 'Boss -> $target';
}
class Boy extends Boss
{
public $target = 'Boy -> $target';
}
?>
执行效果
Fatal error: Cannot declare property Boss::$target final, the final modifier is allowed only for methods and classes in C:WWWindex.php on line 4
final 关键字为什么不允许用于属性?
在 PHP 中,final
关键字只能用于类和方法,不能用于属性。这是 因为属性是对象的状态,它的值可以在运行时发生变化,而 final 关键字的作用是为了限制类和方法的修改和继承,保持其一致性和稳定性
。如果属性可以被声明为 final
,那么子类将无法修改属性的值,这将 违反对象的状态可变的原则
。
另外,属性的可见性可以通过访问修饰符来控制,无需使用 final
关键字来限制属性的继承或修改。
类常量的可见性
类常量
在 PHP 中,类常量是在类定义中被定义的固定值,其值在定义时被设置,并在整个类的生命周期中保持不变。类常量适用于那些在类中具有全局性质,不需要修改的值。类常量可以直接通过类名来访问,而无需创建类的实例。
声明类常量
使用 const
关键字来声明类常量,通常位于类的顶部,在任何方法之外(类常量需要于顶层作用域中声明,否则,PHP 将抛出错误信息)
。常量名称通常使用大写字母,并且可以使用下划线来分隔单词。
class MyClass {
const CONSTANT_NAME = 'constant value';
}
访问类常量
可以使用类名和作用域解析操作符 ::
来访问类常量。常量的访问不需要使用 $
符号。
echo MyClass::CONSTANT_NAME;
常量的可见性
可以使用访问修饰符(public
、protected
、private
)来指定常量的可见性。默认情况下,常量被视为公共成员(public
)的,可以在任何地方访问。
class MyClass {
public const PUBLIC_CONSTANT = 'public';
protected const PROTECTED_CONSTANT = 'protected';
private const PRIVATE_CONSTANT = 'private';
}
类常量的特性
类常量是类的属性,不属于类的实例
。它们在类定义时被设置,并且在整个类的生命周期中保持不变。类常量的值可以是字符串、数值或布尔值等。
class Math {
const PI = 3.14;
const MAX_VALUE = 100;
}
类常量与访问修饰符
在 PHP 中,类常量可以使用访问修饰符(public
、protected
、private
)来指定它们的可见性,这与类属性和类方法的可见性控制方式是一样的。
使用访问修饰符来声明类常量的可见性是从 PHP 7.1.0
版本开始引入的。在此之前的版本,类常量都是公共(public
)的,并且不能使用访问修饰符来指定其可见性。
举个栗子
<?php
class MyClass
{
protected const SECRET = 'Secret';
function spy()
{
# 在类的内部对类常量进行访问
echo self::SECRET;
}
}
class Boy extends MyClass
{
function backdoor()
{
# 在 MyClass 的子类中访问位于父类的受保护成员
echo MyClass::SECRET;
}
}
# 由于 MyClass::SECRET 为 MyClass 类的受保护成员
# 因此,无法在类的外部直接访问,
# 取消 echo '【Outside】' . MyClass::SECRET;
# 代码的注释,PHP 将抛出异常错误信息。
//echo '【Outside】' . MyClass::SECRET;
//echo "
";
# 通过调用实例方法 spy() 对类常量间接进行访问
$myClass = new MyClass();
$myClass -> spy();
echo "
";
# 通过子类间接访问父类中的受保护成员
$visitor = new Boy();
$visitor -> backdoor();
?>
执行效果
Secret
Secret