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嘛,大家都知道的,可以拿到的)。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。