1

访问控制与继承

 3 years ago
source link: https://segmentfault.com/a/1190000040702073
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

访问控制与继承

[TOC]

本文验证分析得到的结论

  • 类内调用,不受访问控制约束限制。类间调用,则受访问控制约束限制。
  • 只有 public 修饰的成员,才能在类外被访问。即实例化对象只可以直接访问被 public 修饰的成员。(注意:当类中声明魔术方法 __get (), 实例化对象访问类未定义或私有的成员是间接访问。)
  • 子类与父类中同时定义一个同名的方法。

    • 父类中的同名方法被 private 修饰,子类中的同名方法与父类的同名方法,不构成重写(overriding)关系。并且①如果父类中有其他公有方法调用此同名方法②子类不重写调用此同名方法的公有方法,那么子类的实例化对象访问这个公有方法,公有方法里面调用的还是父类的同名方法。(其他文章参考:从父类继承的函数中的$ this仍然指向php中的父对象?)
    • 父类中的同名方法被 public 或 protected 修饰,子类中的同名方法与父类的同名方法,构成重写(overriding)关系,但要求修饰子类的同名方法的访问修饰符的作用范围 >= 修饰子类的同名方法的访问修饰符的作用范围,否则报致命错误。一般代码编辑器也会提示。
  • $this 是一个伪变量,表示对主叫对象的引用。当一个方法在类定义内部被调用时,可使用 $this。
  • __CLASS__ 魔术常量,表示当前类。作用与 get_class() 相同,要想获取主叫对象的所属类的类名,可使用 get_called_class() 函数, 或者 get_class($this) 。

类中的成员(属性或类常量或方法)前无任何访问修饰符修饰时,默认被 pubic 修饰。

访问控制修饰符作用范围是否参与继承public当前类内部、子类内部、当前类外部、子类外部YESprotected当前类内部、子类内部YESprivate只能在当前类内部被访问NO

注意:
只有参与继承(被 public 或 protected 修饰)的成员方法,才可以 overriding(重写)。因为 private 修饰的私有方法对于子类来说,是不可见的,即***几乎可以认为私有方法不参与继承***。此外,php 中的 (overload)重载,并不是真正意义上的、像其他语言(如java)所描述的那种概念。 PHP 重载

下面的代码写在名为 base.php 的文件中,运行环境 PHP 8。

<?php
class Super
{
    public $name; //姓名
    public $gender; //性别
    protected $birth; //出生日期
    private $telephone; //手机号码

    // 构造方法
    public function __construct($name, $gender, $birth, $telephone)
    {
        $this->name = $name;
        $this->gender = $gender;
        $this->birth = $birth;
        $this->telephone = $telephone;
    }

    // 私有方法
    private function printHello() {
        echo __CLASS__ . ' hello' . PHP_EOL;
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        $this->printHello();
    }
}

class child extends Super
{
    // 声明一个公有的与父类中私有方法 printHello 同名的方法 printHello,看是否构成对的父类中的同名方法的重写
    private function printHello() {
        echo "阿凡提de小毛驴";
    }
}

$init = ['绘梨衣', '女', '2003-08-12', '158xxxx0812'];
$super = new Super(...$init);
$child = new child(...$init);
echo $super->printTest();
echo $child->printTest();

运行上面的代码,结果输出:

[Running] php "base.php"
Super hello
Super hello

[Done] exited with code=0 in 0.086 seconds

将上面的代码精简一下, 重点分析子类继承父类的公有或受保护的方法中,调用父类的私有方法,而后又在子类中声明同名的私有方法, $this 引用的疑问

第一种情况:子类直接继承父类,子类内部不实现任何方法。

<?php

class Super
{
    // 私有方法 printHello
    private function printHello() {
        echo get_called_class() . ' hello' . PHP_EOL;
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        var_dump(get_class($this));
        var_dump(get_class_methods($this));
        $this->printHello();
    }
}

class child extends Super
{ 
    
}

$super = new Super();
echo $super->printTest();
echo '------------------------------------'.PHP_EOL;
$child = new child();
echo $child->printTest();

输出的结果如下。从输出结果来看,我们知道子类继承了父类的全部方法,包括 Super 私有的方法 printHello()。显然这并不符合我们的预期,因为在其他强类型面向对象语言中,private 修饰的方法被描述为不可继承的。这里我们先可以认为这是因为 php 底层实现导致的原因,先不予考虑,也几乎认为在 php 中,私有方法不能被继承。对这里有疑问的同学可先参考这篇文章,PHP内核探索:继承,多态与抽象类

基于这样的考虑,我认为子类中只存在一个公有方法 printTest(),在类外,Child 的实例化的对象 $child 在调用 printTest() 时,会由于 Child 中没有可访问的 printHello() 方法,发出 Fatal Error。然而,和我预料的不一致。$child->printTest() 时,printTest() 内部的 $this->printHello() 访问的是父类的私有方法 printHello(),且此时的 $this 是 $child 对象的引用。

这不禁让人疑惑,子类的实例化对象的引用 $this 怎么可以访问父类的私有方法?这不科学。

string(5) "Super"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
Super hello
------------------------------------
string(5) "child"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
child hello

第二种情况:子类声明一个与父类私有方法同名的 printHello() 方法

注意:此方法并不与父类中的 printHello() 构成重写关系。

<?php

class Super
{
    // 私有方法 printHello
    private function printHello() {
        echo get_called_class() . ' hello' . PHP_EOL;
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        var_dump(get_class($this));
        var_dump(get_class_methods($this));
        $this->printHello();
    }
}

class child extends Super
{ 
    public function printHello() {
        echo "阿凡提de小毛驴";
    }
}

$super = new Super();
echo $super->printTest();
echo '------------------------------------'.PHP_EOL;
$child = new child();
echo $child->printTest();

结果输出如下。从结果可以看出,$child->printTest() 时,此时的 $this 是 $child 对象的引用,printTest() 内部的 $this->printHello() 访问的依然是父类的私有方法 printHello(),而非子类中声明的 printHello()。这又让人更加疑惑了。

string(5) "Super"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
Super hello
------------------------------------
string(5) "child"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
child hello

第三种情况:子类重写父类公有方法 printTest() ,但函数体不变。

<?php

class Super
{
    // 私有方法 printHello
    private function printHello() {
        echo get_called_class() . ' hello' . PHP_EOL;
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        var_dump(get_class($this));
        var_dump(get_class_methods($this));
        $this->printHello();
    }
}

class child extends Super
{ 
    public function printTest() {
        $this->printHello();
    }
}

结果输出如下。此时的结果是符合预期的,$child->printTest() 是发生致命错误。因为 Child 中并没有可以访问的 printHello() 方法。但即使这样,对比第一种情况,也出现了新的疑问?子类中重写的 printTest() 与 继承的 printTest() 实现上是一样的,为什么第一种代码不报错,而当前的代码会报错,是因为继承的 printTest() 和 重写的 printTest() 在内存中存储的位置不一样吗?

string(5) "Super"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
Super hello
------------------------------------
Fatal error: Uncaught Error: Call to private method Super::printHello() from context 'child' on line 26
Error: Call to private method Super::printHello() from context 'child'

第四种情况:子类声明一个与父类私有方法同名的 printHello() 方法,并且子类重写父类公有方法 printTest() ,但函数体不变。

<?php

class Super
{
    // 私有方法 printHello
    private function printHello() {
        echo get_called_class() . ' hello' . PHP_EOL;
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        var_dump(get_class($this));
        var_dump(get_class_methods($this));
        $this->printHello();
    }
}

class child extends Super
{ 
    public function printHello() {
        echo "阿凡提de小毛驴";
    }

    // 声明一个公有方法,调用上面的私有方法 printHello
    public function printTest() {
        $this->printHello();
    }
}

$super = new Super();
echo $super->printTest();
echo '------------------------------------'.PHP_EOL;
$child = new child();
echo $child->printTest();

此时结果输出如下。我想此种情况是最没有疑问的,也是符合预期的。

string(5) "Super"
array(2) {
  [0] =>
  string(10) "printHello"
  [1] =>
  string(9) "printTest"
}
Super hello
------------------------------------
阿凡提de小毛驴

此问题已在我在本网站发起的问答中得到了解答

PHP 中 $this 在继承时引发的问题?

参考文章:
php继承相关的一个问题
PHP静态绑定与动态绑定
静态绑定和动态绑定的区别
关于php面向对象动态绑定和静态绑定的理解


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK