首頁 > 後端開發 > php教程 > PHP基礎教學十一之封裝、繼承、多型

PHP基礎教學十一之封裝、繼承、多型

黄舟
發布: 2023-03-06 09:00:02
原創
1703 人瀏覽過

本節講解的內容

  • #繼承

  • 多態

  • 重載

  • #重寫

前言

PHP的物件導向和JAVA的物件導向一樣,都分為三大特徵,封裝,繼承,多型。這三個特徵把物件導向進行了許多方面的最佳化。這三大特徵也是在開發物件導向的時候需要考慮的問題。

封裝

在物件導向中什麼是封裝呢?

封裝:把抽像出來的資料和對資料的操作封裝在一起,資料被保護在內部,程式的其他部分只有透過被授權的操作(成員方法),才能對資料進行操作。

上面有提到抽象,也就是把一類事物共有屬性和行為(方法)提取出來,形成一個模板(類), 這種研究問題的方法,我們稱為抽象。

就像我們的銀行帳號,不管是誰的帳號,都包含帳號,密碼,同時也有一些共同的方法,提款,存款,查詢餘額。我們用封裝的想法就是:

<?php
    class Account{

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

        public function __construct($account_no, $pwd = &#39;123456&#39;, $balance = 0.0){

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

        }

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

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

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

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

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

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

    }

    $account = new Account(123456789, &#39;hsp123&#39;, 2000000);

    $account->deposit(30000, &#39;hsp123&#39;);

    $account->query(&#39;hsp123&#39;);

    $account->withdraw(99999900, &#39;hsp123&#39;);

    $account->query(&#39;hsp123&#39;);
登入後複製

上面的程式碼就是銀行業務的封裝,透過封裝程式碼。我們操作,只需要呼叫裡面提供的方法就可以了,不用管理裡面的業務是怎麼處理的。這是一個重要的想法。而我們實作封裝的方法是使用存取修飾符,利用存取修飾符的存取特性,把不讓外部使用的屬性或方法封裝起來。而在上節我們講過三種訪問​​修飾符。
那我們要怎麼存取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 &#39;没有这个属性&#39;;
            }
        }

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

    $cat = new Cat(&#39;小白&#39;,2);
    $cat -> name = &#39;小花&#39;;
    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(&#39;12345&#39; == $password){
                return $this -> name;
            }else{
                echo &#39;密码不对&#39;;
            }
        }
    }

    $cat = new Cat(&#39;小白&#39;,2);
    $cat -> setName(&#39;小花&#39;);
    echo $cat -> getName(&#39;12345&#39;);
    ......结果......
    小花
登入後複製

使用這種方法可以對傳進去的資料進行判斷。我們推薦使用這種方法。

第三種

使用一個統一的方法,對屬性進行運算。

<?php

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

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

    $cat = new Cat(&#39;小白&#39;,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 &#39;<br> 学生的信息如下:&#39;;
            echo &#39;<br> 学生的名字是:&#39; . $this->name;
            echo &#39;<br> 学生的成绩是:&#39; . $this->grade;
        }
        //设置成绩
        public function setGrade($grade){
            $this->grade = $grade;
        }

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

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

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

        public function showInfo(){

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

    //使用一下
    $pupil1 = new Pupil(&#39;小明&#39;, 40);
    $pupil1->testing();
    $pupil1->setGrade(100);
    $pupil1->showInfo();
    echo &#39;<br>&#39;;
    //使用
    $graduate1 = new Graduate(&#39;小华&#39;, 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 &#39;<br> 学生的信息如下:&#39;;
            echo &#39;<br> 学生的名字是:&#39; . $this->name;
            echo &#39;<br> 学生的成绩是:&#39; . $this->grade;
        }
        //设置成绩
        public function setGrade($grade){
            $this->grade = $grade;
        }

    }


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

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

    class Graduate extends Student{

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

    $pupil1 = new Pupil(&#39;小明&#39;, 40);
    $pupil1->testing();
    $pupil1->setGrade(100);
    $pupil1->showInfo();
    echo &#39;<br>&#39;;
    $graduate1 = new Graduate(&#39;小华&#39;, 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 &#39;父类的构造函数&#39;;
            }
        }
    
        class B extends A{
    
        }
        //子类没有定义构造函数,就会执行父类的构造函数。
        $b = new B();
        .....结果......
        父类的构造函数
    登入後複製
  • 如果在子类中需要访问其父类的方法(构造方法/成员方法 方法的访问修饰符是public/protected),可以使用父类::方法名(或者 parent::方法名 ) 来完成

    <?php
        class A{
            public function __construct(){
                echo &#39;父类的构造函数<br>&#39;;
            }
        }
    
        class B extends A{
            //子类定义了自己的构造函数,不会调用父类的构造函数。
            public function __construct(){
                parent::__construct();
                echo &#39;子类的构造函数<br>&#39;;
            }
        }
        //子类没有定义构造函数,就会执行父类的构造函数。
        $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 &#39;对象<br>&#39;;
        }
        //参数是数组
        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 &#39;<br> 猫猫名字是&#39; . $this->name;
        }
    }
    //狗类
    class Dog extends Anmial{
        public function showInfo(){

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

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

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

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

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

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


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

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

        }
    }

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

    $fish = new Fish(&#39;鲨鱼&#39;);
    $bone = new Bone(&#39;骨头&#39;);

    $peach = new Peach(&#39;桃子&#39;);

    $master = new Master(&#39;小明&#39;);

    $master->feed($dog, $bone);
    echo &#39;<br><br>&#39;;
    $master->feed($cat, $fish);
    echo &#39;<br><br>&#39;;
    $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 == &#39;add&#39;){
                //得到数组里面的参数,确定几个参数
                $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 &#39;<br>&#39;;
    echo $calculate -> add(1,2,3);
    .....结果......
    3
    6
登入後複製

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

属性的重载

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

<?php

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

    }
    $a = new A();
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    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 &#39;吃饭<br>&#39;;
        }
    }

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

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

在方法的重写中:

  1. 子类的方法的参数个数 ,方法名称,要和父类方法的参数个数,方法名称一样

  2. 子类方法不能缩小父类方法的访问权限(可以大于可以等于)

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

属性的重写

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

<?php

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

    class Cat extends Animal{
        public $name = &#39;小白&#39;;
        protected $age = 4;
        private $sex = &#39;雄&#39;;
    }
    $cat = new Cat();
    echo &#39;<pre class="brush:php;toolbar:false">&#39;;
    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号,祖国的生日,我要去给祖国母亲庆生去了)

 以上就是PHP基础教程十一之封装、继承、多态的内容,更多相关内容请关注PHP中文网(www.php.cn)!


來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板