ホームページ  >  記事  >  バックエンド開発  >  PHP 基礎チュートリアル 11: カプセル化、継承、ポリモーフィズム

PHP 基礎チュートリアル 11: カプセル化、継承、ポリモーフィズム

黄舟
黄舟オリジナル
2017-03-01 10:01:281641ブラウズ

このセクションで説明される内容

  • カプセル化

  • 継承

  • ポリモーフィズム

  • オーバーロード

  • 執筆

まえがき

PHP オブジェクト指向Java のオブジェクト指向と同じで、カプセル化、継承、ポリモーフィズムの 3 つの主要な機能に分かれています。これら 3 つの機能は、多くの面でオブジェクト指向を最適化します。これら 3 つの特性は、オブジェクト指向を開発するときに考慮する必要がある問題でもあります。

カプセル化

オブジェクト指向の用語でカプセル化とは何ですか?

カプセル化: 抽象化されたデータとそのデータに対する操作を一緒にカプセル化します。データは内部的に保護されており、承認された操作 (メンバー メソッド) を通じてのみデータを操作できます。

抽象化とは、物事のクラスに共通する属性や動作 (メソッド) を抽出してテンプレート (クラス) を形成することです。この問題を研究する方法は抽象化と呼ばれます。

私たちの銀行口座と同じように、誰の口座であっても、口座番号とパスワードが含まれており、お金の引き出し、入金、残高の確認のための一般的な方法もいくつかあります。カプセル化を使用するという私たちのアイデアは次のとおりです:

<?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;);

上記のコードは、カプセル化コードによる銀行業務のカプセル化です。操作するときは、内部で提供されているメソッドを呼び出すだけでよく、内部のビジネスがどのように処理されるかを管理する必要はありません。これは重要な考え方です。カプセル化を実装する方法は、アクセス修飾子を使用し、アクセス修飾子のアクセス特性を使用して、外部での使用が許可されていないプロパティまたはメソッドをカプセル化することです。前のセクションでは、3 つのアクセス修飾子について説明しました。
では、保護された非公開のメンバー プロパティにアクセスするにはどうすればよいでしょうか?ここには 3 つの方法があります。

最初の

は、マジックメソッド__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のプロパティを使用し、保護された属性の値を変更しますが、このメソッドは推奨されません使用。十分な柔軟性がないため、受信データを検証できません。次のメソッドで受信データを検証できます。

2 番目の

は、各プライベートまたは保護されたメンバー変数に対して 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;);
    ......结果......
    小花

このメソッドを使用して、受信データを判断します。この方法をお勧めします。

3 番目のタイプ

は、統一されたメソッドを使用して属性を操作します。

<?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

これら 3 つのメソッドのうち、開発では 2 番目と 3 番目のメソッドをよく使用します。複数の属性を操作する場合は 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 キーワードを通じて親クラスを継承するクラスをさらに 2 つ作成します。
継承するクラスを親クラス、このクラスを継承するクラスを子クラスといいます。
継承している限り、親クラスのプロパティとメソッドを使用できます。ただし、親クラスの 属性またはメソッドが 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)!


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。