OOP 中的继承规则基本上是银河系常识:public/protected 可继承,private 不可继承。

大家平时有没有思考下这种情景:

父类的某 public 方法中访问了本类的 private 方法或属性。子类继承并调用此方法,同时子类重新定义或重写了此方法中需要访问的
private 方法或属性,那此方法会访问到我们子类中重写给定的 private 方法或属性吗?

先给出答案:

不会。private 属性/方法 是与定义类紧密结合在一起的,而 public/protected 属性/方法
则是以占位符的方式可匹配替换的。子类其实存储着父类的私有成员,但是不会暴露出访问接口来(python除外),而我们在子类中重新定义 private 成员时并不能覆盖父类的(可以看下面的私有成员的键值名)。

class Foo
{
    public $name;
    protected $sex;
    private $age;

    public function __construct($name, $sex, $age)
    {
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
    }
}

// object(Foo)#1 (3) {
//   ["name"]=>string(7) "big_cat"
//   ["sex":protected]=>string(4) "male"
//   ["age":"Foo":private]=>int(27)
// }
var_dump(new Foo("big_cat", "male", 27));

可以看出 public/protected 属性和类并没显示的标识关联,而 private 则和定义类关联在了一起,当你在子类中定义了与父类私有变量或方法相同的标示符时,他们只是在字面上长的一样而已,在编译器/解释器里是两个完全不同的存在。

当子类没有重写继承自父类的某方法时,执行上下文会切换至父类的此方法中,而此方法中访问的私有属性或方法是与当前类固化在一起的,而子类中重新定义的字面上一样的 private 属性或方法与之毫无关系。

当然,如果你重写了此方法,其访问的私有属性或方法也就与当前类固化在一起了。

下面给个具体的例子

父类 Person

<?php

class Person
{
    public $name;
    
    private $age; //私有属性的标识 "age":"Person":private 可以看出定义类是紧密联系在一起的

    public function __construct($name, $age)
    {
        $this->name = $name;
        $this->age  = $age; //"age":"Person":private
    }

    public function getName()
    {
        return $this->name;
    }

    /**
     * 获取私有属性 age
     * @return [type] [description]
     */
    public function getAge()
    {
        return $this->age; //"age":"Person":private
    }

    private function getInfo($join_str) // "getInfo":"Person":private
    {
        return $this->name . $join_str . $this->age; //"age":"Person":private
    }

    public function publicFoo()
    {
        $this->privateBar(); // "privateBar":"Person":private
    }

    private function privateBar()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

子类 Boy

<?php

class Boy extends Person
{
    private $age; //"age":"Boy":private

    public function setName($name)
    {
        $this->name = $name;
    }

    public function setAge($age)
    {
        $this->age = $age; //"age":"Boy":private
    }

    // 子类重写父类的方法
    // 注意:方法的访问权限一定要比父类中的 weaker
    public function getInfo($join_str, $endline = "")
    {
        return $this->name . $join_str . $this->age . $endline; //"age":"Boy":private
    }

    // 重写父类的 private 方法
    // "privateBar":"Boy":private
    private function privateBar()
    {
        echo __METHOD__ . PHP_EOL;
    }
}

示例:

<?php

// Person 实例
// age 属性与 Person 关联在了一起
// object(Person)#1 (2) {
//   ["name"] => string(7) "big_cat"
//   ["age":"Person":private]=> int(27)
// }
var_dump(new Person("big_cat", 27));

// Boy 实例
$big_cat = new Boy("big_cat", 27);

// 其实 Boy 中有 Person 的 age 属性
// object(Boy)#1 (3) {
//   ["age":"Boy":private] => NULL
//   ["name"] => string(7) "big_cat"
//   ["age":"Person":private] => int(27)
// }
var_dump($big_cat);

$big_cat->setName("bigger_cat");
// age 已在 Boy 中重新声明(严谨的说法应该是 Boy 定义了一个自己的 age 属性)
// 这里 setAge 操作的其实是 "age":"Boy":private 对 "age":"Person":private 没有影响
$big_cat->setAge(18);

// 可以看到 setAge 的作用
// object(Boy)#1 (3) {
//   ["age":"Boy":private]=>int(18)
//   ["name"]=>string(7) "bigger_cat"
//   ["age":"Person":private]=>int(27)
// }
var_dump($big_cat);

// Boy 继承自父类的 getAge 方法,上下文会切换到父类解释执行 getAge 方法
// 父类 getAge 中的 age 属性为 "age":"Person":private
// 所以并不会读取 Boy 自身的 age 属性 "age":"Boy":private
var_dump($big_cat->getAge());

// 私有方法
// privateBar 虽然已被 Boy 重写,但 publicFoo 的调用还是切换到了父类 Person
// Person 中调用的为 privateBar:Person:private 方法
// 无法替换为 Boy 中的 privateBar:Boy:private 方法
echo $big_cat->publicFoo();

我觉得在这块儿 python 会更容易表现也好玩很多,因为 java/php 什么的我真的不知道怎么在 Boy 中访问 Person 的 private 属性,当然本身就是不允许访问的,编译器/解释器是不会让我如此嚣张的....

而 python 正如它将属性或方法私有化的方式:

定义时以双下划线开头(且不以双下划线结尾)的属性或方法将被私有化,但 python 解释器并不禁止你执着的访问这些私有化的成员。约定并不是禁止,_CLASS__foo 我想大家都知道的。

来个小实例:

# -*- coding: utf-8 -*-

class Person(object):

    'Person class doc'

    def __init__(self, name, age, sex):
        self.name  = name
        self._age  = age
        self.__sex = sex

    def getSex(self):
        return self.__sex # 此处的 self.__sex 永远为 _Person__sex

class Boy(Person):

    def setSex(self, sex):
        self.__sex = sex # 此处的 self.__sex 为 _Boy__sex 而非 _Person__sex


lilei = Boy("lilei", 25, "male")
# 而这里是在 Boy 中定义的方法,访问到的私有属性其实为 _Boy__sex 而非 _Person__sex
lilei.setSex("female")
# 执行上下文切换至父类的 getSex 中,其内部访问的为 _Person__sex 属性 所以获取的还是 male
print(lilei.getSex())

# but 我其实可以手动访问到父类的私有属性
lilei._Person__sex = "female"
# 这时候获取到的就是 female
print(lilei.getSex())

继承权限作用下的隐式约束
    一、方法继承权限对方法重写的约束
        子类可以继承父类的 public/protected 的方法,无法继承 private 方法(宇宙常识)
        子类重写父类方法时,方法的访问级别要 weaker 父类中此方法的访问级别(太阳系常识)
        父类              子类
        public            public
        protected         protected/public
        private           private/protected/public

    二、类的私有属性的本质
        子类无法继承父类的私有属性(宇宙常识)
        那么 private/__ 修饰的属性本质上是怎样的存在呢?
        给个自然语言的小示例:
        class Foo
            private name/__name

        私有属性是这样被标示的:name:Foo:private/_Foo__name
        所以私有属性和其所属类是紧密联系在一起的,所以不可能被子类继承
        子类可能会声明标示符相同的属性,但只是标示符相同,标示并不相同

        如上例所示:
        __sex 为 python 中的 "私有" 变量,其真实的标示为 _Person__sex

        当 Boy 继承 Person,并试图 setSex 来更改 __sex 属性
        当调用继承自父类的 getSex 方法时,返回的值仍未实例化时的值,setSex 并未改变

        1/ 子类调用继承自父类的 getSex() 会将执行的上下文切换至父类,解释并执行父类中的此方法
        此方法返回的为父类的属性 _Person__sex,而子类的 setSex() 设定的是自身的私有属性 _Boy__sex
        所以得不到期望的结果,其他属性为公有,会根据子类是否重新定义或赋值而更新

        2/ 而当我们将 getSex() 在子类中重写后,此时的 self.__sex 为 _Boy__sex,而非 _Person__sex
        是可以通过子类中的 setSex() 更新的

其实父类的私有成员在子类中也是存在的,只不过 java/php 等不会将其暴露出来(Python嘛,大家都知道的,可以拿到的)。


big_cat
1.7k 声望130 粉丝

规范至上


引用和评论

0 条评论