【PHP】面向对象-继承相关知识

关于继承

继承extends,是指子类通过继承可以访问父类的成员。

基本语法

class 子类 extends 父类{}

继承目标:继承本质是针对同类有包含关系的共性继承,即父类通常是包含子类,子类属于父类。所以在父类中通常定义的是子类共有的一些特性成员,这是开发者默认遵循的规则


有限继承

有限继承,指子类在继承父类的成员的时候,并非继承所有内容,而是继承并使用父类部分内容。

继承的内容:

PHP中继承是子类继承父类所有的公有成员受保护成员私有成员,不能继承父类的私有方法

  • 子类可以在内部访问父类的保护和公有方法,但是不能访问父类的私有方法,子类只能在外部访问父类的公有方法

  • 父类私有成员虽然被你继承下来了,但是你不能直接访问!

  • 访问父类私有成员:子类若想访问父类私有成员,那么前提是父类提供了能够访问私有成员的接口:即提供了公有或者受保护的方法给子类访问

//父类
class Human{
    const CALL = '人';
    public $name = 'human';
    protected $age = '100';
    private $money = '100';
    
    public function showName(){
        echo $this->name;
    }
    
    protected function showAge(){
        echo $this->age;
    }
    
    private function showMoney(){
        echo $this->money;
    }
}

//子类
class Man extends Human{} 
//实例化子类
$m = new Man();

//object(Man)#1 (3) { ["name"]=> string(5) "human" ["age":protected]=> string(3) "100" ["money":"Human":private]=> string(3) "100" }
var_dump($m);				//可以看到父类私有属性  ==>所有的属性(成员)都可以看到

$m->showName();				//允许直接访问:方法为公有允许类外访问

关于父类protected成员

protected关键字的产生本身就是纯用于继承的,表示允许被子类在子类内部访问的意思,而不允许子类在外部直接访问这个保护成员。

class Human{
    protected $age = '100';
    protected function showAge(){
        echo $this->age;
    }
}

//子类
class Man extends Human{
    //在子类内部增加公有访问访问继承自父类的受保护成员
    public function getProtected(){
        echo $this->age;				//访问父类受保护属性
        $this->showAge();				//访问父类受保护方法
    }
}

关于静态成员和类常量

静态成员和类常量也遵循继承规则,只是访问方式是由类进行访问

class Human{
    const NAME = '人';
    public static $count = 0;
    protected static $type = array('黑','黄','白');
    
    public static function getCount(){
        echo self::NAME;
        echo self::$count;
    }
    protected static function getType(){
        print_r(self::$type);
    }
}

class Man extends Human{
    //依然需要子类中访问受保护成员
    public static function getHuman(){
        Human::getType();
    }
}

echo Man::$count;	//允许直接访问
Man::getCount();	//访问父类静态方法
Man::getHuman();	//利用子类公有方法访问父类受保护成员
echo Man::NAME;  	//允许访问父类类常量

关于父类私有成员

子类若想访问父类私有成员,那么前提是父类提供了能够访问私有成员的接口:即提供了公有的方法给子类访问,否则子类不能直接访问父类的私有成员

class A
{
    private $name = "mango";
    public function GetName()
    {
        echo $this->name;
    }
}
class B extends A 
{}
$b->name;
$b->GetName();

关于构造方法和析构方法

构造方法和析构方法也可以被子类继承,此时需要注意子类对象实例化时对应的父类构造方法的参数

//父类
class Human{
    private $money;
    public function __construct($money){
        var_dump($this)
        $this->money = $money;
    }
    
    public function __destruct(){
        echo 'die';
    }
}

//子类继承
class Man extends Human{}

//子类实例化:自身是空类,没有指定构造方法
//$m = new Man();					//错误:缺少参数,因为会自动调用父类构造方法
$m = new Man(100);					//正确

重写-override

重写,即子类中定义了与父类重名的成员,子类可以重写父类任意类成员,通常重写是用来重写父类的方法,用于扩展或者更改某些业务逻辑。

1.子类继承父类,同时子类定义与父类同名的类成员

2.重写父类成员之后,子类只会直接访问子类的成员(覆盖)

注意:不管是公有和是受保护属性,一旦重写,父类的就会不存在,而私有属性不会被覆盖,因为子类本身也不能访问父类的私有属性

class A
{
    private $name = "mango";
    public $age = 20;
    protected $sex = 'man';
    public function show()
    {
        echo __CLASS__;
    }
}
class B extends A 
{
    private $name = "lemon";
    public $age = 21;
    protected $sex = 'boy';
    public function show()
    {
        echo __CLASS__;
    }
}
$b = new B();
var_dump($b);
/*
object(B)#1 (4) { 
    ["name":"B":private]=> string(5) "lemon" 
    ["age"]=> int(21) 
    ["sex":protected]=> string(3) "boy" 
    ["name":"A":private]=> string(5) "mango" 
}
*/

$b->show(); //B

重写的要求:

1:子类重写父类的方法 ,控制权不能高于父类,即子类可以比父类更开放 private > protected > public

  • 比如父类的控制权为protected,那么子类的控制权不能为private,只能为protected 或者public
//父类
class Human{
    protected function show(){
        echo __CLASS__,'<br/>';
    } 
}

//子类继承
class Man extends Human{
    //重写
    protected function show(){}				//正确
    public function show(){}				//允许
    private function show(){}				//错误:控制权比父类更严格
         
}

2:PHP中重写要求子类重写父类方法的时候,必须保证与父类同名方法参数一致

注意:在方法参数一致不单单要求数量一致,而且数据类型要求也必须相同,但形参名字可以不同;另外,在PHP7以前重写对于参数这块没有要求

//父类
class Human{
    protected function show(){
        echo __CLASS__,'<br/>';
    }
}

//子类继承
class Man extends Human{
    //重写
    public function show(){}
    public function show($a){}			//错误,与父类同名方法不一致
}

3:重写针对的是被继承的成员,父类私有方法不会被继承,因此不受要求2规定

//父类
class Human{
    private function show(){
        echo __CLASS__,'<br/>';
    }
}

//子类
class Man extends Human{
    private function show($name){	//不会报错,因为本质不存在重写(父类Human::show没有被继承)
        echo $name,'<br/>';
    }
}

4.重写是指子类拥有特殊的情况,一般是需要在父类的基础上进行扩展,此时如果想要继续保证父类被重写的方法继续执行(默认永远只访问子类重写的新方法),需要在子类重写方法的时候使用parent关键字

  • parent::父类方法()

注意:parent专门用来访问父类被重写的方法至于是不是在同名的方法里面使用无所谓parent不能访问父类的普通成员,可以访问静态成员、静态方法、类常量和普通方法

//父类
class Human{
    protected function show(){
        echo __CLASS__,'<br/>';
    }
}

//子类继承
class Man extends Human{
    //重写
    public function show(){
        //强制调用父类被重写方法
        parent::show();
        
        //扩展业务逻辑
        echo __CLASS__,'<br/>';
    }
}

继承特点

1.PHP中继承只能单继承:即子类只有一个父类(有些语言支持多继承)

2.PHP若想继承多个类,可以使用链式继承

3.PHP中继承只有私有方法不能被继承,其它的都可以被继承,包括私有属性,但是该私有属性,子类在类内和类外都不能访问,只能通过父类提供的公有方法进行访问

  • 而对于父类的保护成员,子类可以在类内访问,但是不能在类外进行访问
class Man{}
class Woman extends Man{}
class Ladyboy extends Woman{}	//Ladyboy包含了Man和Woman类中所有可继承的成员

4.PHP允许子类继承父类的构造方法和析构方法


静态延迟绑定

1静态延迟绑定对比的是静态绑定self ,使用static关键字代替self进行类成员访问,

2.静态延迟绑定一定是通过继承后的子类来进行访问才有效果

3.静态延迟绑定是指通过static关键字进行类静态成员的访问,是指在被访问时才决定到底使用哪个类,也就是根据调用类的不同而选择不同的表现

  • 使用self的时候是访问该类静态成员,与调用类无关
//父类
class Human{
    public static $name = 'Human';
    public static function showName(){
        //静态绑定
        echo self::$name,'<br/>';
    }
}
//子类
class Man extends Human{
    //重写父类静态属性
    public static $name = 'Man';	//静态属性因为存储在类内部,因此不会覆盖
}

//子类访问
Man::showName();					//输出Human
Human::showName();					//输出Human
  • 如果使用static,此时会根据调用类的不同而选择不同的表现
//父类
class Human{
    public static $name = 'Human';
    public static function showName(){
        //静态绑定
        echo static::$name,'<br/>';
    }
}
//子类
class Man extends Human{
    //重写父类静态属性
    public static $name = 'Man';	//静态属性因为存储在类内部,因此不会覆盖
}

//子类访问
Man::showName();					//输出Mang
Human::showName();					//输出Human

最终类

1.最终类:使用final关键字修饰类名,表示此类不可以被继承

  • final修饰类表示不希望类再出现子类,可以很好保护类的内部结构不被暴露
//最终类
final class Man{}

class Man18 extends Man{}		//致命错误:无法从final类继承

2.final关键字不止修饰类表示类不可被继承,还能修饰方法,表示方法不能被重写

  • final修饰方法表示不希望方法被修改,可以在一个更高的维度来保证同类事务的共同表现
//父类
class Human{
    public function show(){}		//普通方法
    public final function walk(){}	//最终方法
}
//子类
class Man extends Human{
    //重写
    public function show(){}		//没问题
    public function walk(){}		//致命错误:不能重写父类中的最终方法
}

抽象类

1.抽象类:使用abstract关键字修饰的类,表示该类只能被继承,不能被实例化

//抽象类
abstract class Human{}
$h = new Human();				//致命错误,抽象类不能被实例化
class Man extends Human{}		//抽象类只能被继承

2.abstract关键字还可以用来修饰方法(抽象方法),抽象方法所在的类必须是抽象类,抽象方法不能有方法体

//抽象方法抽象类
abstract class Human{
    //定义抽象方法:没有方法体
    abstract public function eat();
    public function show(){}			//普通方法有方法体
}

3.抽象方法因为要被子类继承实现,所以不能使用private修饰(私有方法不会被继承)

//抽象类
abstract class Human{
    //抽象方法
    abstract private function eat();		//错误:抽象方法不能私有化
}

4.子类继承抽象类后:有抽象方法的抽象类被继承时子类要么自身是抽象类,要么实现所有抽象方法

  • 方法1:子类也声明为抽象类,这样就不用实现父类的抽象方法了,但是此时子类就无法实例化出对象,只能被其他类继承了
  • 方法2:子类实现抽象方法(所有抽象方法)
//抽象方法抽象类(父类)
abstract class Human{
    //定义抽象方法:没有方法体
    abstract public function eat();
    public function show(){}			//普通方法有方法体
}

//子类1:抽象类继承抽象类
abstract class Man extends Human{}		//正常继承


//子类2:子类实现父类所有抽象方法
class Boy extends Man{
    //实现从祖父类继承的eat抽象方法
    public function eat(){
        echo 'eat';
    }
}

5.抽象类的目的是用来规范子类(通常必配抽象方法)


接口Interface

1.接口:使用interface关键字定义,与类类似,专门用来规范一些共性类必须实现的方法

2.接口不是类,不可以被实例化

3.接口实现:接口是用来规范类必须完成的事情,所以接口只能被类实现implements

  • 类实现接口的时候,必须实现接口中所有的抽象方法(或者抽象类实现)

4.接口成员:接口中只能定义公有抽象方法和接口常量,不能有其它属性

  • 在接口当中,方法默认就是自带abstract关键字
interface Human{
    //接口常量
    const NAME = '人';
    //接口抽象方法
    public function eat(); 
    
    //错误示例  
    public function go(){}				//错误:接口中的方法必须为抽象
    public $age;					   //错误:接口中不能有属性
    public static $count = 0;			//错误:接口中不能有静态属性(成员属性)
    protected function walk();			//错误:接口方法必须为公有抽象方法
}
class Man implements Human{}
new Human();				//致命错误,接口不能被实例化

5.接口成员方法必须被子类实现或者该类为抽象类,接口常量可以直接在实现类中方法

interface Human{
   //接口常量
    const NAME = '人';
    //接口抽象方法
    public function eat(); 
}

//方法1:实现接口
class Man implements Human{
    //必须实现接口所有抽象方法
    public function eat(){
        echo self::NAME;				//可以访问接口的 常量
    }
}
echo Man::NMAE; 	//没问题
//方法2:抽象类实现接口
abstract class Ladyboy implements Human{}		//正常实现,因为抽象类可以有抽象方法

6.实现接口的类成员,不允许重写接口中的常量,不允许增加接口方法的控制权限

class Woman implements Human{
    //重写接口常量
    const NAME = '女人';			  //错误:不允许重写接口常量
    
    //强化接口方法控制
    private function eat(){}		//错误:接口方法不允许使用其他访问修饰限定符,必须使用public
}

7.接口可以继承接口:extends,而且接口可以多继承接口

  • 此时如果有类实现Ape的方法:那么必须实现Ape,Huma,Animal当中的抽象接口
interface Human{
    public function walk();
}

interface Animal{
    public function eat();
}
//单继承
interface Man extends Human{}
//多继承
interface Ape extends Human,Animal{}

8.接口是在更大型的项目中,为了保证底层的实现而设定的规范,通常是抽象类实现接口,增加必要成员,然后让实际业务类去继承抽象类


trait代码复用

1.trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制,trait是一种类似class的关键字

2.trait内部可以拥有一个类能拥有成员属性(包含静态),成员方法(包含静态),但不能有类常量

3.trait是用来实现代码的复用的,不可以被实例化也不可以被继承(因为trait不是类)

4.trait是用来将公共代码提供给其他类使用的,而类要使用trait的前提是加载对应的trait

trait Eat{
    public function show(){
        echo 'eat';
    }
}

//类中加载trait
class Human{
    //加载:使用use关键字
    use Eat;				//use就表示将trait Eat中的所有东西拿到了当前类Human中
}

//使用trait中的内容
$h = new Human();
$h->show();					//eat:Human类自己没有show方法,但是因为使用了trait Eat,所以可用

5.一个类可以使用多个traits,但是如果同时引入的多个trait中有同名方法,那么会产生冲突:解决冲突的方法是使用insteadof代替处理以及对被替代方法使用别名

冲突代码

trait t1{
    public function eat(){
        echo 't1,eat';
    }
}
trait t2{
    public function eat(){
        echo 't2,eat';
    }
}

class Human{
    use t1,t2;				//错误:eat()方法冲突
}

解决方法1:明确替代

//解决方案1:明确替代
class Person{
    use t1,t2{					//花括号
        t2::eat insteadof t1;	 //t2的eat代替t1的eat
    }
}
$p = new Person();
$p->eat();						//t2,eat;
//但此时就没办法让Person对象访问t1的eat方法了

解决方法2:先替换后别名

//解决方案2:先替换后别名
//一个替代另一个,然后另外一个取别名
class Animal{
    use t1,t2{  
        t1::eat insteadof t2;	//明确使用t1中的eat方法
        t2::eat as eat2;		//t2中的eat方法改名叫eat2
    }
}
$a = new Animal();
$a->eat();						//t1,eat
$a->eat2();						//t2,eat

6.同名覆盖问题:如果类中有与引入的trait同名成员,会有不同处理

  • 成员变量:不允许重名,即类中不允许定义与trait中同名的成员变量(静态变量也一样)
  • 方法:类中的方法覆盖引入的trait中的方法
trait Eat{
    public $food = '米饭';
    public function show(){
        echo $this->food;
    }
}

class Human{
    use Eat;
    
    //定义同名属性
    //public $food = '面条';		//错误
    
    //定义方法
    public function show(){
        echo 'show';
    }
}

$h = new Human();
$h->show();						//show:类覆盖掉trait

7.继承覆盖问题:如果类中在使用trait的同时,也是继承自父类,而trait中与父类中有同名方法,那么trait中将覆盖父类同名方法;如果要访问父类方法,可以在trait同名方法中使用parent关键字访问父类同名方法

  • 优先级:类自己 > trait > 基类
trait Eat{
    public function eat(){
        //如果想访问父类的eat方法: 		 parent::eat();
        echo 'Eat::eat';
    }
}
class Human{
    public function eat(){
        echo 'Human::eat';
    }
}
//子类继承父类同时使用trait
class Man extends Human{
    use Eat;
}

$m = new Man();
$m->eat(); //此时输出的是Eat::eat

8.允许类在使用trait时更改里面的方法的访问控制权:在as之后,使用目标访问修饰限定符

trait Eat{
    private function show(){
        echo 'eat';
    }
}
class Human{
    use Eat{
        //注意:as是用来设定别名的,虽然没有同名show,但是系统认为show已经存在,所以必须别名
        //所以:  show as public show;//err
        show as public  eshow;				
    }
}
$h = new Human();
$h->eshow();								//eat

上述show方法要改变权限,是把他变成一个新的方法,从而改变权限,原来的方法是不会被改变的

9.trait中可以使用抽象方法,用来规范使用类必须实现对应抽象方法:使用类要么为抽象类,要么就必须实现抽象方法

trait Eat{
    public function eat();		//抽象方法
}
abstract class Human{			  
    use Eat;					//抽象类:可以不实现抽象方法
}

class Animal{
    use Eat;
    public function eat(){		//具体类:必须实现抽象方法
        echo 'Animal::eat';	
    }		
}

对象遍历-foreach

遍历对象,其实就是指将对象中的所有属性(公有属性)以键值对的形式取出并进行访问

1.foreach可以对对象像数组一样遍历

2.foreach遍历对象遍历的是对象内部的所有公有属性(在类外部进行对象遍历),如果是在类内部,此时可以访问对象的所有属性

header('Content-type:text/html;charset=utf-8');
class Test
{
    public $name = 'Mango';
    public $age = 21;
    protected $sex = 'male';
    private $money = '∞';
    //类内遍历
    public function PrintObject()
    {
        foreach($this as $key => $val)
            echo $key . " ==> " . $val,'<br/>'; 
    }
}
$m = new Test();
$m->PrintObject();

echo '<hr/>';
foreach($m as $key => $val)
    echo $key . " ==> " . $val,'<br/>'; 

/*输出:
name ==> Mango
age ==> 21
sex ==> male
money ==> ∞
name ==> Mango
age ==> 21
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芒果再努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值