这是我参与更文挑战的第8天,活动详情查看: 更文挑战
前两节介绍类构成要素的时候, 都提到了两个东西: 可见度和继承.
成员变量, 成员方法和类常量这三者有都可以被继承, 而可见度又决定了这三者的继承效果. 所以我们把可见度和继承放在一起讲.
基础知识
先说继承extend, 继承是子类自动共享父类属性和方法的机制,这是类之间的一种关系。
父类 − 一个类被其他类继承,可将该类称基类, 有根基, 基础的意思。
子类 − 一个类继承其他类称为子类,也可称为派生类, 是对父类的延申.
关于继承, 有以下几个基本规则:
-
PHP 不支持多重继承,一个类只能继承一个父类, 但一个父类可以又多个子类.
-
子类也可以是其他类的父类. PHP中支持多级继承.
-
父类必须在子类之前被声明。此规则适用于类继承其它类与接口。
下面这个例子表达了子类与父类的关系, 以及多级继承:
class A {}
class B extends A {}
class C extends B {}
class D extends B {}
$someObj = new A();
$someOtherObj = new B();
$anotherObj = new C();
$lastObj = new D();
//B是A的子类, C和D都是B的子类, 那么C和D同样也是A的子类
复制代码
再让我们重温一下可见度 Visibility 的限制条件:
-
public, 就是可以公开的, 没有必要隐藏的数据信息, 可以在程序中的任何位置被其他类和对象调用, 子类可以继承和使用父类中的所有公共成员
-
private私有的,被 private 关键字修饰的成员变量和成员方法, 只能在所属类的内部被调用和修改,不可以在类外被访问, 在子类中也不可以被调用或修改
-
protected受保护的, 修饰的成员变量和方法, 只能被其自身以及其子类和父类的内部访问。
-
如果没有写可见度关键词, 那么系统会默认为public
那么我们可以获知, 子类能继承的只有父类非私有的属性和方法. 我们看下面这个例子:
class Woshinibaba
{
private $private='fatherprivate'; //定义私有属性
protected $protected='fatherprotected'; //定义受保护的属性
public $public='fatherpublic'; //定义公共属性
private function fatherprivate() //定义私有方法
{
echo $this->private; //访问私有属性;
echo $this->protected.'<br>'; //访问受保护属性;
echo $this->public.'<br>'; //访问公共属性;
}
protected function fatherprotected() //定义受保护的方法
{
echo $this->private;
echo $this->protected.'<br>';
echo $this->public.'<br>';
}
public function fatherpublic() //定义公共的方法
{
echo $this->private;
echo $this->protected.'<br>';
echo $this->public.'<br>';
}
}
class Test7 extends Woshinibaba
{
public function childpublic() //定义子类公共方法
{
echo $this->public; //访问父类公共属性
echo $this->protected; //访问父类受保护属性
echo $this->private; //访问父类私有属性
}
function childtest() //默认为public属性
{
parent::fatherprotected(); //访问父类受保护方法
parent::fatherprivate(); //报错: 子类无法访问父类私有方法
}
}
$child= new Test7(); //新建子类对象;
$child->fatherprivate(); //报错, 无法在类外访问父类私有方法;
$child->fatherprotected(); //报错, 无法在类外访问父类受保护方法;
$child->fatherpublic(): //输出fatherprivate fatherprotected fatherpublic, 父类方法可以访问自身的private和protected属性;
$child->childpublic(); //输出fatherpublic fatherprotected, 以及Undefined property:$private, 因为无法从子类访问父类的private变量;
$child->childtest(); //只有fatherprotected()可以正常运行;
echo $child->public; //fatherpublic, 公共属性可以在任意位置访问;
echo $child->protected; //报错Cannot access protected property, 无法在类外访问protected属性;
echo $child->private; //报错Undefined property, 无法访问父类私有属性;
复制代码
从上面的例子中, 还有2个关键点:
- 子类可以使用parent::这个代词来访问父类protected方法, parent这里指代当前方法所在类的父类;
- 类的内部指class{}以内的代码, 类的外部指其之外的代码, protected方法无法在类外被调用;
重写override
子类除了可以继承父类的非private成员, 还可以通过用同样的名字覆盖父类的成员, 已实现不同的功能, 这就是重写。子类可以根据自己的情况, 对父类的属性和方法进行重写, 或增加只属于自己的属性和方法.
方法重写
我们先来举例看一下子成员方法重写:
class Base1
{ //公共方法, 包含2个强制参数, 1个可选参数
public function foo(int $a, $b, $c='test') { }
final public function bar() //定义final方法
{
echo 'test';
}
}
class Extend1 extends Base1
{ //子类可以将父类的必填参数改为可选参数, 并新增可选参数, 修改参数默认值, 可以删除参数类型, 并且制定返回类型
function foo($a, $b=10, $c='done', array $d=[]): float
{
return $DDD=$a*$b; //子类重写方法体
}
function milk()
{
parent::foo(); //子类依然可以调用父类方法;
}
}
class Extend2 extends Base1 //报错, 不能将参数$a类型改为float;
{
function foo(float $a=5, $b, $c='test') {}
}
class Extend3 extends Base1
{
function foo(int $a, $b, $c) {} //报错, 不能将可选参数$c变为必填参数
function bar() //报错, 无法重写父类final方法;
{
echo 'done';
}
}
class Extend4 extends Base1
{ //报错, 不能将父类public方法重写为protected
protected function foo(int $a, $b, $c=10) {}
}
class Extend5 extends Base1
{ //报错, 不能将父类非static方法重写为static
static public function foo(int $a, $b, $c=10) {}
}
复制代码
好的, 我们根据上面的例子得出重写的基本规则:
-
当子类重写父方法时,子类可以将父类强制参数改为可选参数, 子类可以新增可选参数;
-
当子类重写父方法时,子类不能新增强制参数, 不能移除参数、不能修改可选参数为强制参数, 不能将参数类型改为其他类型, 但却可以删除父类的参数类型;
-
当子类重写父方法时,子类方法返回值类型必须和父类的保持一致。如果父类没有指定返回类型,那么子方法可以指定自己的返回类型。
-
子类重写父类方法时, 如果父类方法是public则子类方法只能是public. 如果父类方法是protected, 则子类可以是public或protected. 我上面的例子不太全, 大家可以自己在本地测试一下.
-
子类重写父类方法后, 依然可以通过 parent:: 来访问被覆盖的方法。
-
如果父类“很强势”,不想自己的方法被子类覆盖重写,可以使用final关键字来修饰该方法。
-
子类重写父类方法时, 子类方法的static状态必须和父类一致, 如果父类是static, 则子类方法必须也是static, 如果父类是non-static, 则子类也必须是non-static; 关于静态的详细知识我们下一节讲.
总结一下就是子类在重写方法的时候, 对于参数, 可见度的限制条件一定要和父类一致或更宽松(weaker). 返回值除外, 子类的返回值可以比父类更严格.
我们再看当父类的方法可见度是private的时候, 以下情况都是正常的:
class Baro
{ //定义父类private方法
private function testPrivate() {}
}
class Fool extends Baro
{ //定义子类同名方法 protected可见度, 并增加强制参数$a
protected function testPrivate($a){}
}
class fool1 extends Baro
{ \\定义子类同名方法 private可见度
private function testPrivate($b){}
}
class fool2 extends Baro
{ //定义子类同名方法, public可见度, 并增加返回类型
public function testPrivate($d):array{}
}
复制代码
通过上面这个例子, 父类定义了私有方法, 而父类的私有方法是无法继承给子类的, 所以子类可以做任意的重写(新增), 完全没有限制.
属性重写
子类重写父类属性时, 遵循一个原则: 子类属性的可见度必须和父类一致或更宽松.
我们来看个实例:
class MyClass1
{
public $public = 'Public';
protected $protected = 'Protected';
private $private = 'Private';
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj = new MyClass1();
echo $obj->public; // 这行能被正常执行
echo $obj->protected; //报错: 无法在类外直接访问受保护的属性
echo $obj->private; //报错: 无法在类外直接访问私有的属性
$obj->printHello(); //PublicProtectedPrivate, 均可以在本类内部访问
class MyClass2 extends MyClass1
{
private $private = 'Private2'; //新增私有属性, 正常
public $public = 'Public2'; //重写父类公共属性, 正常
protected $protected = 'Protected2'; //重写父类protected属性, 正常
function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj2 = new MyClass2();
$obj2->printHello(); //Public2 Protected2, Private2;
class MyClass3 extends MyClass1
{
public $private = 'Private3' //正常
protected $public = 'Public3'; //报错: 可见度必须是public
public $protected = 'Protected3'; //正常, 可以重写为public
class MyClass4 extends MyClass1
{
static public $public = 'Public4'; //报错: 无法重写为static属性;
复制代码
好的, 到这里我们总结一下属性重写的规则:
-
子类的static属性必须和父类保持一致;
-
如果父类属性是public, 则子类属性只能是public;
-
如果父类属性是protected, 则子类可以是public 或 protected;
-
如果父类属性是private, 则子类可以是任何属性, 因为父类私有属性在子类是不可见的, 所以不会影响子类新建同名属性.
父类Private属性
但如果我们把上例中的$obj2打印出来, 会发现这里面有一个坑:
print_r($obj2);
//output:
MyClass2 Object
( [private:MyClass2:private] => Private2 //子类同名私有属性
[public] => Public2
[protected:protected] => Protected2
[private:MyClass1:private] => Private ) //父类私有属性同时存在
复制代码
可以看到, 作为子类对象, 它包含了两个同名属性attr, 一个是它自己的私有属性, 另一个实际上是继承了父类private属性. 而父类其他类型的属性都被覆盖重写看不到了. 也就是说只有父类的私有变量是不会被子类覆盖重写的, 并且也会被子类继承.
那么此时当子类对象被父类方法调用, 会出现什么情况? 我们看下面这个例子:
class POP
{ //定义父类私有属性, 以及公共方法打印属性;
private $attr = "father";
public function getFields()
{
echo get_class($this);
var_dump($this);
return $this->attr;
}
}
class COC extends POP
{ //定义子类同名属性, 并继承父类方法;
protected $attr = "Child";
}
$obj = new COC();
var_dump($obj->getFields()); //调用父类方法, output:father
复制代码
上面这个例子中, 只要父类的属性是private, 不管子类属性可见度是什么, 输出的都是”father”, 这与常理不符. 是$this发生错乱失效了么?
当然不是, 从$obj打印信息中我们知道, 当父类属性是private时, 子类都会继承并保留这个父类的值, 而且不会被覆盖重写. 此时对象中存在两个同名属性attr.
两个同名属性, 该如何输出呢? PHP的解决方案是: 当被父类方法调用时, $this->attr指代的对象就是父类私有变量, 当子类方法调用时, 就返回子类对应属性”Child”, 也就是说, 子类方法调用时, 即便会继承父类私有属性, 但$this->attr仍然指向子类的属性. 这种机制可以理解为”就近原则“:
当对象含有两个同名属性的时候, 通过哪个类调用, 就返回那个类的属性.
最后还有一点, 我们上面讲过子类方法可以通过parent::来访问被覆盖的父类方法, 但子类却无法通过parent::来访问被覆盖的父类属性, 除非父类属性是静态的. 好了, 今天说了不少关于静态的事情, 我们下一节就讲讲PHP类中的静态属性和方法.
下课~!
总结不易, 请勿私自转载, 否则别怪老大爷不客气!
参考资料:
www.php.net PHP官方文档
零基础PHP学习笔记 明日科技
PHP父类方法里如何访问子类属性 www.zhai14.com/blog/how-to… 翟码农