最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • PHP类的秘密(三) 可见度, 继承与重写

    正文概述 转载于:掘金(传达室老大爷)   2021-06-25   343

    前两节介绍类构成要素的时候, 都提到了两个东西: 可见度和继承.

    成员变量, 成员方法和类常量这三者有都可以被继承, 而可见度又决定了这三者的继承效果. 所以我们把可见度和继承放在一起讲.

    基础知识

    先说继承extend, 继承是子类自动共享父类属性和方法的机制,这是类之间的一种关系。

    父类 − 一个类被其他类继承,可将该类称基类, 有根基, 基础的意思。

    子类 − 一个类继承其他类称为子类,也可称为派生类, 是对父类的延申.

    关于继承, 有以下几个基本规则:

    1. php 不支持多重继承,一个类只能继承一个父类, 但一个父类可以又多个子类.

    2. 子类也可以是其他类的父类. PHP中支持多级继承.

    3. 父类必须在子类之前被声明。此规则适用于类继承其它类与接口。

    下面这个例子表达了子类与父类的关系, 以及多级继承:

    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 的限制条件:

    1. public, 就是可以公开的, 没有必要隐藏的数据信息, 可以在程序中的任何位置被其他类和对象调用, 子类可以继承和使用父类中的所有公共成员

    2. private私有的,被 private 关键字修饰的成员变量和成员方法, 只能在所属类的内部被调用和修改,不可以在类外被访问, 在子类中也不可以被调用或修改

    3. protected受保护的, 修饰的成员变量和方法, 只能被其自身以及其子类和父类的内部访问

    4. 如果没有写可见度关键词, 那么系统会默认为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个关键点:

    1. 子类可以使用parent::这个代词来访问父类protected方法, parent这里指代当前方法所在类的父类;
    2. 类的内部指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) {}
    }
    

    好的, 我们根据上面的例子得出重写的基本规则:

    1. 当子类重写父方法时,子类可以将父类强制参数改为可选参数, 子类可以新增可选参数;

    2. 当子类重写父方法时,子类不能新增强制参数, 不能移除参数、不能修改可选参数为强制参数, 不能将参数类型改为其他类型, 但却可以删除父类的参数类型;

    3. 当子类重写父方法时,子类方法返回值类型必须和父类的保持一致。如果父类没有指定返回类型,那么子方法可以指定自己的返回类型。

    4. 子类重写父类方法时, 如果父类方法是public则子类方法只能是public. 如果父类方法是protected, 则子类可以是public或protected. 我上面的例子不太全, 大家可以自己在本地测试一下.

    5. 子类重写父类方法后, 依然可以通过 parent:: 来访问被覆盖的方法。

    6. 如果父类“很强势”,不想自己的方法被子类覆盖重写,可以使用final关键字来修饰该方法。

    7. 子类重写父类方法时, 子类方法的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属性;
    

    好的, 到这里我们总结一下属性重写的规则:

    1. 子类的static属性必须和父类保持一致;

    2. 如果父类属性是public, 则子类属性只能是public;

    3. 如果父类属性是protected, 则子类可以是public 或 protected;

    4. 如果父类属性是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… 翟码农


    起源地下载网 » PHP类的秘密(三) 可见度, 继承与重写

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元