PHP基础教程十一之封装、继承、多态

x33g5p2x  于2022-03-06 转载在 其他  
字(10.4k)|赞(0)|评价(0)|浏览(432)

本节讲解的内容

  • 封装
  • 继承
  • 多态
  • 重载
  • 重写

前言

PHP的面向对象和JAVA的面向对象一样,都分为三大特征,封装,继承,多态。这三个特征把面向对象进行了很多方面的优化。这三大特征也是在开发面向对象的时候需要考虑的问题。

封装

在面向对象中什么是封装呢?

封装:把抽象出来的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作(成员方法),才能对数据进行操作。

上面有提到抽象,也就是把一类事物共有属性和行为(方法)提取出来,形成一个模板(类), 这种研究问题的方法,我们称为抽象。

就像我们的银行账号,不管是谁的账号,都包括账号,密码,同时也有一些共同的方法,取款,存款,查询余额。我们用封装的思想就是:

<?php
    class Account{

        public $account_no;
        private $pwd;
        private $balance;

        public function __construct($account_no, $pwd = '123456', $balance = 0.0){

            $this->account_no = $account_no;
            $this->pwd = $pwd;
            $this->balance = $balance;

        }

        //存款
        public function deposit($amount, $pwd){

            if($pwd == $this->pwd){
                echo '<br> 存款成功';
                $this->balance += $amount;
            }else{
                echo '<br> 密码不正确';
            }
        }

        //取款
        public function withdraw($amount, $pwd){
            if($pwd == $this->pwd){

                if($this->balance >= $amount){
                    echo '<br> 取款成功';
                    $this->balance -= $amount;
                }else{
                    echo '<br> 余额不足..';
                }
            }else{
                echo '<br>密码错误';
            }
        }

        //查询
        public function query($pwd){

            if($pwd == $this->pwd){
                echo '<br> 账号' . $this->account_no . ' 余额=' . $this->balance;
            }else{
                echo '<br> 查询密码错误';
            }
        }

    }

    $account = new Account(123456789, 'hsp123', 2000000);

    $account->deposit(30000, 'hsp123');

    $account->query('hsp123');

    $account->withdraw(99999900, 'hsp123');

    $account->query('hsp123');

上面的代码就是银行业务的封装,通过封装代码。我们操作,只需要调用里面提供的方法就可以了,而不用管理里面的业务是怎么处理的。这是一个重要的思想。而我们实现封装的方法是使用访问修饰符,利用访问修饰符的访问特性,把不让外部使用的属性或方法封装起来。而在上节中我们讲过三种访问修饰符。
那么我们怎么访问protected 和 private 成员属性呢?这里有三种方法。

第一种

使用魔术方法__get() 和 __set() 来访问

<?php

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

        public function __set($name,$value){
            //判断类中有没有这个属性
            if(property_exists($this,$name)){
                $this -> $name = $value;
            }else{
                echo '没有这个属性';
            }
        }

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

    $cat = new Cat('小白',2);
    $cat -> name = '小花';
    echo $cat -> name;
    ......结果......
    小花

我们通过魔术方法set和get的特性来改变受保护的属性的值,但是这种方法并不推荐使用。因为不够灵活, 不可以对传入的数据进行校验。而下面的方法就可以对传入的数据进行校验。

第二种

为每个private或者protected 成员变量提供一对getXxx() 和 setXxx() 的方法。

<?php

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

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

        public function getName($password){
            if('12345' == $password){
                return $this -> name;
            }else{
                echo '密码不对';
            }
        }
    }

    $cat = new Cat('小白',2);
    $cat -> setName('小花');
    echo $cat -> getName('12345');
    ......结果......
    小花

使用这种方法可以对传进去的数据进行判断。我们推荐使用这种方法。

第三种

使用一个统一的方法,对属性进行操作。

<?php

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

        //显示对象的信息
        public function showInfo(){
            echo $this -> name . '的年龄是:' . $this-> age;
        }
    }

    $cat = new Cat('小白',2);
    $cat -> showInfo();
    ......结果......
    小白的年龄是:2

这三种方法,我们在开发中第二种和第三种用的比较多,当对单一的属性操作的时候,可以使用第二种,当对多种属性进行操作的时候,可以使用第三种。

继承

在开发中使用继承的情况太多了,继承的出现,减少了代码的冗余性。是代码看起来更加的清晰。那么什么是继承呢?

<?php
    //定义一个小学生,它有考试的方法,设置成绩的方法
    class Pupil {
        public $name;
        public $age;
        private $grade;

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

        public function showInfo(){
            echo '<br> 学生的信息如下:';
            echo '<br> 学生的名字是:' . $this->name;
            echo '<br> 学生的成绩是:' . $this->grade;
        }
        //设置成绩
        public function setGrade($grade){
            $this->grade = $grade;
        }

        public function testing(){
            echo '<br> 小学生在考试.........';
        }
    }

    //定义一个大学生,它有考试的方法,设置成绩的方法
    class Graduate {
        public $name;
        public $age;
        private $grade;

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

        public function showInfo(){

            echo '<br> 学生的信息如下:';
            echo '<br> 学生的名字是:' . $this->name;
            echo '<br> 学生的成绩是:' . $this->grade;
        }
        //设置成绩
        public function setGrade($grade){
            $this->grade = $grade;
        }
        public function testing(){
            echo '<br> 大学生在考试.....';
        }
    }

    //使用一下
    $pupil1 = new Pupil('小明', 40);
    $pupil1->testing();
    $pupil1->setGrade(100);
    $pupil1->showInfo();
    echo '<br>';
    //使用
    $graduate1 = new Graduate('小华', 20);
    $graduate1->testing();
    $graduate1->setGrade(60);
    $graduate1->showInfo();

在上面的代码中我们可以看到两个类中都有相同的属性和方法,如果我们的代码中有很多这样的代码,就会造成代码的冗余,更不利于我们的维护。而解决的最好的方法就是使用继承,从而提高代码的复用性。

继承的语法:

class 方法名 extends 父类的方法名{

}

继承使用关键字extends进行继承的。可以理解为代码的复用,让我们的编程更加靠近人的思维,当多个类存在相同的属性(变量)和方法时,可以从这些类中抽取出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要继承父类就行了

<?php

    //使用继承来完成
    class Student{

        public $name;
        public $age;
        private $grade;

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

        public function showInfo(){

            echo '<br> 学生的信息如下:';
            echo '<br> 学生的名字是:' . $this->name;
            echo '<br> 学生的成绩是:' . $this->grade;
        }
        //设置成绩
        public function setGrade($grade){
            $this->grade = $grade;
        }

    }

    //这里 extends 关键字就是表示 Pupil 类 继承了 Student类
    class Pupil extends Student{

        public function testing(){
            echo '<br> 小学生在考试.........';
        }
    }

    class Graduate extends Student{

        public function testing(){
            echo '<br> 大学生在考试.........';
        }
    }

    $pupil1 = new Pupil('小明', 40);
    $pupil1->testing();
    $pupil1->setGrade(100);
    $pupil1->showInfo();
    echo '<br>';
    $graduate1 = new Graduate('小华', 20);
    $graduate1->testing();
    $graduate1->setGrade(60);
    $graduate1->showInfo();

可以看到上面的代码中把一样的属性和方法都出去到Student类(父类)中,我们再写两个类来通过extends这个关键字来继承父类。
用来继承的类称为父类,而继承这个类的类称之为子类。
只要通过继承,我们就能使用父类里面的属性和方法。但是如果父类的属性或者方法被private修饰,则子类不能够继承,这就是protected和private的区别。

子类通过继承得到父类的属性和方法,那么是不是就以为着把父类的代码赋值了一份拷贝到子类呢?其实并不是的。继承不是简单把父类的属性和方法定义拷贝一份到子类,而是建立了一个查找的关系。而我们再访问时,这种查找关系是:

  1. 当某个对象去操作属性和方法时,首先在本类去看有没有对应的属性和方法, 如果有,就判断是否有访问权限,如果可以访问则访问,如果不可以访问就报错
  2. 当某个对象去操作属性和方法时,首先在本类去看有没有对应的属性和方法, 如果没有, 就会去查找自己的父类, 父类有,再判断是否可以访问,可以访问就访问,不可以访问就报错。
  3. 这个查找的逻辑到顶级类。。。。。

在继承中还是有很多需要注意的地方:

  • 子类最多只能继承一个父类(指直接继承),这和c++是不一样的。
  • 子类可以继承其父类(或者基类)的 public ,protected修饰的变量(属性) 和 函数(方法)
  • 在创建某个子类对象时,默认情况下会自动调用其父类的构造函数(指在子类没有自定义构造函数情况时)
<?php
    class A{
        public function __construct(){
            echo '父类的构造函数';
        }
    }

    class B extends A{

    }
    //子类没有定义构造函数,就会执行父类的构造函数。
    $b = new B();
    .....结果......
    父类的构造函数
  • 如果在子类中需要访问其父类的方法(构造方法/成员方法 方法的访问修饰符是public/protected),可以使用父类::方法名(或者 parent::方法名 ) 来完成
<?php
    class A{
        public function __construct(){
            echo '父类的构造函数<br>';
        }
    }

    class B extends A{
        //子类定义了自己的构造函数,不会调用父类的构造函数。
        public function __construct(){
            parent::__construct();
            echo '子类的构造函数<br>';
        }
    }
    //子类没有定义构造函数,就会执行父类的构造函数。
    $b = new B();
    .....结果......
    父类的构造函数
    子类的构造函数
  • 如果子类中的方法和父类方法相同,我们称为方法重写。

多态

多态通俗的讲就是多种形态,就是指在面向对象中,对象在不同情况下的多种状态(根据使用的上下文)PHP天生就是多态语言,同时PHP可以根据传入的对象类型不同,调用对应对象的方法

在PHP中变量的定义不像其他语言在变量名前面定义类型,而是直接用$符号来定义,可以接受任意类型的值。这就为多态创造了条件。根据传入的对象类型不同,调用对应对象的方法,我们也可以使用类型约束来对传入的值进行约束。

类型约束

类型约束的基本概念是 PHP 5 可以使用类型约束。函数的参数可以指定必须为对象(在函数原型里面指定类的名字),接口,数组(PHP 5.1 起)或者 callable(PHP 5.4 起)。如前PHP只支持这几种。

<?php

    class  MyClass
    {
         //传进的参数约束为cat或者cat的子类       
        public function  test (Cat $cat ){
            echo '对象<br>';
        }
        //参数是数组
        public function  test_array (array  $arr ) {
             print_r($arr);
        }
    }
    class  Cat  {
    }

    $cat = new Cat();
    $myClass = new MyClass();
    $arr = array(1,2,4,5);
    $myClass -> test($cat);
    $myClass -> test_array($arr);
    .....结果......
    对象
    Array ( [0] => 1 [1] => 2 [2] => 4 [3] => 5 )

如果要进行类型约束,在参数前面写上类型就行了,可以试一下传入的不是约束的类型,会报一个致命的错误。

多态的示例:

<?php
    //多态的案例
    //定义动物的父类
    class Anmial{
        public $name;
        public function __construct($name){
            $this->name = $name;
        }
    }
    //猫类
    class Cat extends Anmial{
        public function showInfo(){

            echo '<br> 猫猫名字是' . $this->name;
        }
    }
    //狗类
    class Dog extends Anmial{
        public function showInfo(){

            echo '<br> 狗名字是' . $this->name;
        }
    }
    //猴子类
    class Monkey extends Anmial{
        public function showInfo(){

            echo '<br> 猴子名字是' . $this->name;
        }
    }

    //定义事物
    class Food{
        public $name;
        public function __construct($name){
            $this->name = $name;
        }
    }

    class Fish extends Food{
        public function showInfo(){
            echo '<br> ' . $this->name;
        }
    }

    class Bone extends Food{
        public function showInfo(){
            echo '<br> ' . $this->name;
        }
    }

    class Peach extends Food{
        public function showInfo(){
            echo '<br>' . $this->name;
        }
    }

    //主人类
    class Master{
        public $name;
        public function __construct($name){
            $this->name = $name;
        }
        //主人可以喂食
        //当我们的类型约束是父类时,可以接受 他的子类的对象实例
        public function feed(Anmial $aniaml, Food $food){

            echo '<br> 主人 ' . $this->name;
            $aniaml->showInfo(); //使用多态,根据传入的值不同,调用不同的方法。
            echo '<br> 喂得食物是';
            $food->showInfo();

        }
    }

    $dog = new Dog('黄狗');
    $cat = new Cat('花猫');
    $monkey = new Monkey('猴');

    $fish = new Fish('鲨鱼');
    $bone = new Bone('骨头');

    $peach = new Peach('桃子');

    $master = new Master('小明');

    $master->feed($dog, $bone);
    echo '<br><br>';
    $master->feed($cat, $fish);
    echo '<br><br>';
    $master->feed($monkey, $peach);

上面的代码在主人的喂食方法中,使用类型约束,根据传进去的对象不同,调用不同对象的方法。

重载

方法的重载

重载:简单说,就是函数或者方法有相同的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。

上面的关于方法的重载可以理解为在一个类中,有两个方法名一样的函数,但是函数的参数是不一样的,我们在调用的时候,系统会根据我们传入的参数的不同,而自动的调用不同的函数,这就是重载,但是在PHP中一个类中不能有两个方法名相同的函数,尽管你的参数不同,它会报一个Cannot redeclare的错误。那么在PHP中就不能重载了吗?其实可以的,利用魔术方法。

在上节中介绍魔术方法中有一个方法是当我们访问不可访问或不存在的方法时,系统自动调用的,也就是__call()方法。PHP中可以利用这个魔术方法进行重载

<?php
    class Calculate{

        //定义两个方法,计算加法,注意两个方法的方法名是不一样的
        private function add($a,$b,$c){ 
            return $a + $b + $c;
        }

        private function add1($a,$b){
            return $a + $b;
        }

        public function __call($name,$val_arr){
            if($name == 'add'){
                //得到数组里面的参数,确定几个参数
                $num = count($val_arr);
                if($num == 2){
                    return $this -> add1($val_arr[0],$val_arr[1]);
                }else if($num == 3){
                    return $this -> add($val_arr[0],$val_arr[1],$val_arr[2]);
                }
            }
        }
    }

    $calculate = new Calculate();
    echo $calculate -> add(1,2);
    echo '<br>';
    echo $calculate -> add(1,2,3);
    .....结果......
    3
    6

看到代码有没有被欺骗的感觉-_-,先把类中的两个方法设置成private,这样在类外就访问不到,当我们在类外访问add方法的时候,会调用魔术方法,然后通过传入的数组的个数,确定参数的个数,从而在魔术方法中去掉用合适的方法。这就是PHP的方法重载。

属性的重载

在PHP面向对象编程中,当你去给一个不存在的属性赋值时,PHP默认会’动态的’, 给你创建一个对应的属性,这个称为属性重载。

<?php

    class A{
        //在类中只定义一个变量
        public $name = '小明';

    }
    $a = new A();
    echo '<pre>';
    var_dump($a);
    $a -> age = 12;
    var_dump($a);
    .....结果......
    object(A)#1 (1) {
      ["name"]=>
      string(6) "小明"
    }
    object(A)#1 (2) {
      ["name"]=>
      string(6) "小明"
      ["age"]=>
      int(12)
    }

从结果中可以看到,当我们给一个不存在属性age赋值后,在输出的类结构中出现了age这个属性,这就是属性的重载。

如果不想让类的属性自动增加,可以使用魔术方法__set()和__get()方法进行控制。

重写

方法的重写

在上面我们介绍了面向对象的继承机制。而重写是基于继承才引发出来的概念。当一个类继承了另外一个类后,在父类中有一个方法,子类继承,但是在子类中觉得父类的方法不能满足要求,需要子类在重写定义一个和父类的方法一样的方法进行重新的定义,称为重写。说白了就子类有一个方法,和父类(基类)的某个方法的名称、参数个数一样。

<?php

    class Animal{
        //动物都有吃这个行为,具体的要看是什么动物
        public function eat(){

            echo '吃饭<br>';
        }
    }

    class Cat extends Animal{
        //对父类的方法进行重写
        public function eat(){
            echo '猫在吃饭<br>';
        }       
    }
    $cat = new Cat();
    $cat -> eat();
    .....结果......
    猫在吃饭

如果父类的方法中使用了类型约束,那么子类的类型约束也必须一样。

在方法的重写中:

  1. 子类的方法的参数个数 ,方法名称,要和父类方法的参数个数,方法名称一样
  2. 子类方法不能缩小父类方法的访问权限(可以大于可以等于)

注意:如果父类的方法名是private,则子类并不会进行重写。

属性的重写

对于属性的重写也是,public 和 protected 可以被重写,private 的属性不能被重写

<?php

    class Animal{
        public $name = '小花';
        protected $age = 12;
        private $sex = '雄';
    }

    class Cat extends Animal{
        public $name = '小白';
        protected $age = 4;
        private $sex = '雄';
    }
    $cat = new Cat();
    echo '<pre>';
    var_dump($cat);
    ......结果......
    object(Cat)#1 (4) {
      ["name"]=>
      string(6) "小白"
      ["age":protected]=>
      int(4)
      ["sex":"Cat":private]=>
      string(3) "雄"
      ["sex":"Animal":private]=>
      string(3) "雄"
    }

可以看到name和age被重写了,private不能被重写。

总结

面向对象中,封装是一个很重要的思想,封装的实现,可以降低代码的耦合度,同时继承的时候减少代码的冗余度,php的独特语言结构让多态也散发着光芒,而重写的掌握,让我们对继承有了更深层次的了解。(ps:今天10.1号,祖国的生日,我要去给祖国母亲庆生去了)

相关文章