PHP 面向对象篇:继承、封装与多态
2023-06-03 加入收藏
1、继承
所谓继承,指的是子类可以通过继承的方式访问父类的属性和方法(protected
或者 public
方式定义),在 PHP 中,继承通过 extends
关键字实现,我们以上篇教程编写的 Car
类为例,编写一个实现该类的子类 Benz
(仍然在 class.php
中定义):
class Benz extends Car
{
public function __construct($seats = 5, $doors = 4, $engine = 1)
{
$this->brand = '奔驰';
// $this->setBrand('奔驰'); // 也可以通过该方法设置
parent::__construct($this->brand, $seats, $doors, $engine);
}
}
这里 extends Car
的含义就是 Benz
继承自 Car
,是它的子类,相对的,Car
是 Benz
的父类。
在子类 Benz
的构造函数中,我们将品牌设置为「奔驰」,然后通过 parent::__construct
调用父类的构造函数进行初始化(调用父类的同名方法需要通过 parent::
进行调用,否则 PHP 会不知道调用父类还是子类的方法),这样,初始化 Benz
对象时,就无须传入品牌参数了。
可以看到,在子类中可以通过 $this
对象直接访问父类定义的属性和方法,前提是该属性或方法的可见性是 protected
或者 public
级别,如果试图访问 private
声明的属性或方法,PhpStorm 会警告:
运行代码也会报错。
另外,我们也可以通过子类对象访问父类方法(在子类函数体中访问父类方法,通过 $this
即可):
$benz = new Benz();
$benz->drive();
上述代码的执行结果如下:
可以看到子类可以继承父类所有通过 protected
和 public
声明的属性和方法,并且在调用过程中自动将 $this
指针引用指向子类对象,对于 public
属性和方法,和父类一样,直接可以在类外部通过 ->
操作符调用。
当然,你也可以在子类中新增一些独有的属性和方法:
class Benz extends Car
{
private $customProp = "自定义属性";
...
public function customMethod()
{
echo "Call custom prop \$customProp: " . $this->customProp . PHP_EOL;
echo "This is a custom method in Benz Class" . PHP_EOL;
}
}
PHP 遵循单继承机制,即一个子类只能继承自一个父类。
2、封装
概念解释
封装一方面指的是调用者无需关心对象方法实现细节,比如我们要开车,就调用 $car->drive()
方法即可,不用编写具体的实现逻辑,也不用去关心(调用了那些属性、那些方法、不管是私有的还是公开的、当前类的还是其他类的,统统不用关心),就像我们在真实世界中开车一样,只需要按照流程来操作就好了,不用关心汽车引擎内部是如何工作的。
另一方面是通过访问控制限定属性和方法的可见性,比如 public
修饰的属性和方法所有地方可见,不管是当前类、子类还是类之外,protected
修饰的属性和方法在当前类和子类中可见,而 private
修饰的属性和方法仅在当前类可见,你可以根据自己的业务需要合理的设置属性和方法的可见性。
反射
不过,饶是如此,依然可以通过反射的方式将 protected
或者 private
级别的属性和方法变成类以外可以访问,比如我们将 Benz
类中的 customMethod
方法设置为私有的:
class Benz extends Car
{
private $customProp = '自定义属性';
...
private function customMethod()
{
echo "Call custom prop \$customProp: " . $this->customProp . PHP_EOL;
echo "This is a custom method in Benz Class" . PHP_EOL;
}
}
在类外直接调用会报错:
我们通过反射来调用这个方法,可以这么做:
// 通过反射调用非 public 方法
$method = new ReflectionMethod(Benz::class, 'customMethod');
$method->setAccessible(true);
$benz = new Benz();
$method->invoke($benz);
打印结果和调用声明为 public
的 customMethod
方法完全一样,如果将 private
修改为 protected
效果也一样,通过反射,我们可以在运行时以逆向工程的方式对 PHP 类进行实例化,并对类中的属性和方法进行动态调用,不管这些属性和方法是否对外公开,所以这是一个黑科技,更多反射的细节可以参考 PHP 官方文档:https://www.php.net/manual/zh/book.reflection.php。
3、多态
方法重写
所谓多态,指的是在 PHP 继承体系中,子类可以重写父类的同名方法,这样,在子类对象中调用该方法,就会自动转发到子类方法调用,还是以 Car
和 Benz
为例,我们在子类中重写父类的 drive
方法(所谓重写,英文是 override,即在子类中编写和父类同名方法,来覆盖父类的实现):
class Benz extends Car
{
...
// 重写父类实现
public function drive()
{
echo $this->getBrand() . '汽车的启动流程:' . PHP_EOL;
parent::drive(); // TODO: Change the autogenerated stub
}
}
我们在子类的 drive
方法中,先打印了一段提示文本,然后和构造函数一样,通过 parent::drive
调用父类的同名方法,因为所有的汽车启动流程基本都是一样的。
接下来,我们通过子类对象调用 drive
方法:
$benz = new Benz();
$benz->drive();
打印结果如下:
包含了第一行提示文本,所以,调用的是子类的方法而不是父类的。
类型转化
PHP 不像 Java 那样支持同一个类中定义多个同名方法(参数数量或类型不同,这种叫做方法重载),另外,由于子类一定包含了父类的公开方法,所以当类作为参数类型声明时,如果声明类型为父类,则可以传入子类对象,反过来,如果声明类型为子类,则不能传入父类对象。
比如我们定义一个测试汽车类启动功能的测试类和方法如下,并编写一段测试代码:
class TestCarDrive
{
public function testDrive(Car $car)
{
$car->drive();
}
public function testBenzDrive(Benz $benz)
{
$benz->drive();
}
}
// 初始化类对象
$bmw = new Car('宝马');
$benz = new Benz();
$test = new TestCarDrive();
// 测试子类转父类
$test->testDrive($benz);
// 测试父类转子类
$test->testBenzDrive($bmw);
上述代码第一个测试 $test->testDrive
可以正常运行,第二个会报错: