1. 程式人生 > >PHP面向物件程式設計:面向物件概念、基本實踐、高階實戰、PHP面向物件特殊實踐

PHP面向物件程式設計:面向物件概念、基本實踐、高階實戰、PHP面向物件特殊實踐

一、面向物件的概念

1.1 什麼是面向物件(object oriented)

     世間萬物皆物件,抽象的也是物件,一切可見或不可見都是物件

1.2 物件的基本組成

     物件包含兩個部分:

  • 物件的組成元素

    • 是物件的資料模型,用於描述物件的資料
      又稱為物件的屬性,或者物件的成員變數
  • 物件的行為

    • 是物件的行為模型,用於描述物件能夠做什麼事情
      又被稱為物件的方法

1.3 物件特點

  • 每一個物件都是獨一無二的
  • 物件是一個特定的事物,他的職能是完成特定功能
  • 物件是可以重複使用

1.4 面向物件簡介

  1. 面向物件程式設計
    就是程式設計的時候資料結構(資料組織方式 )都通過物件的結構進行儲存,使用屬性方法組織起來
  2. 為什麼要使用面向物件程式設計?
    物件的描述方式更加貼合真實世界,有利於對大型業務的理解

1.5 面向物件的實質

  1. 面向物件就是把生活中要解決的問題都用物件的方式進行儲存--把所有的資料用屬性方法表現出來。
  2. 物件之間的互動是通過方法的呼叫完成互動

1.6 面向物件的基本思路

  1. 識別物件
    任何實體都可以被識別為一個物件
  2. 識別物件的屬性
    物件裡面儲存的資料被識別為屬性
    對於不同的業務邏輯,關注的資料不同,物件裡面儲存的屬性也不同
  3. 識別物件的行為
    物件自己的屬性資料的改變
    物件外部的互動

1.7 面向物件的基本原則

  1. 物件內部高內聚
    物件只負責一項特定的職能(職能可大可小)
    所有物件相關的內容都封裝到物件內部
  2. 物件外部低耦合
    外部世界可以看到物件的一些屬性(並非全部)
    外部世界可以看到物件可以做某些事情(並非全部)

     軟體設計儘可能的做到:高內聚,低耦合,模組與模組間應該是獨立的,沒有依賴關係

二、面向物件的基本實踐

2.1 類的概念

  1. 物以類聚,把具有相似特性的物件對壘到一個類中
  2. 類定義了這些相似物件擁有的相同的屬性和方法
  3. 類是相似物件的描述,成為類的定義,是該類物件的藍圖或者原型
  4. 類的物件稱為一個類的例項(Instance)
  5. 類的屬性和方法統稱為類成員

2.2 類的例項化

  • 類的例項化:通過類定義建立一個類的物件
  • 類的定義屬性值都是空或預設值,而物件的屬性都有具體的值

2.3 類的定義

  1. 類的定義以關鍵字class開始,後面跟著這個類的名稱。類的命名通常每個單詞的第一個字母大寫,以中括號開始和結束
  2. 類的例項化為物件時使用關鍵字newnew之後緊跟類的名稱和一對括號
  3. 物件中得成員屬性和方法可以通過->符號來訪問

2.4 建構函式

  1. 預設建構函式在物件被例項化的時候自動呼叫
  2. $thisPhp裡面的偽變數,表示物件本身。可以通過$this->的方式訪問物件的屬性和方法
  3. 每一次用new例項化物件的時候,都會用類名後面的引數列表呼叫建構函式
  4. php類函式的建構函式function __construct(){}執行時自動呼叫

2.5 解構函式

  1. function __destruct(){}解構函式是根據後入先出的原則
  2. 兩種方式會被執行解構函式:物件被設定未null或者程式結束時會被自動呼叫解構函式,,所佔用的資源被系統回收
  3. 解構函式通常被用於清理程式使用的資源,比如釋放開啟的檔案等等
  4. 解構函式在該物件不會再被使用的情況下自動呼叫,如果被複制了,而不是&引用,就不會呼叫解構函式

2.6 物件&引用的基本概念

$james1 = $james; //相當於複製出來多一個引用,兩者是獨立的兩個引用
$james2 = &$james; //相當於為james取一個別名,兩者其實是一體的,只是有兩個名字

     特別注意:

  1. PHP 永遠會將物件按引用傳遞ArrayObject 是一個SPL物件,它完全模仿陣列的用法,但是卻是以物件來工作)
  2. $arr = array(); $arr2 = $arr; $arr2$arr陣列的一份拷貝,它們之間互不影響,是獨立的兩個陣列
  3. &物件(陣列)都是相當於起別名

深入理解PHP引用:常見錯誤 #3:關於通過引用返回與通過值返回的困惑

三、面向物件的高階實戰

3.1 物件的繼承

    父類:擁有部分相同的屬性和方法

    繼承的好處

  1. 父類裡面定義的類成員可以不用在子類中重複定義,節約了程式設計的時間和代價
  2. 同一個父類的子類擁有相同的父類定義的類成員,因此外部程式碼呼叫他們的時候可以一視同仁
  3. 子類可以修改和呼叫父類定義的類成員

    • 我們稱為重寫Overwrite
    • 一旦子類修改了,就按照子類修改之後的功能執行

    子類:

  1. 子類可以通過$this訪問父類的屬性
  2. 子類的物件可以直接呼叫父類的方法和屬性
  3. PHP單繼承特性:類不允許同時繼承多個父類(extends後面只能跟一個父類名稱)

3.2 訪問控制

    面向物件的三種訪問許可權:

  1. public是公有的類成員,可以在任何地方被訪問

    • 可以被類以及子類或者物件都可以訪問
  2. protected受保護的類成員,可以被其自身以及繼承的子類訪問

    • 可以被子類繼承,但是不能被物件訪問,只能通過封裝的方式讓物件訪問
  3. private私有的類成員,只能被自身訪問

    • 不能被子類繼承,也不能被物件訪問,只能在自身通過封裝讓外界訪問(例如在類裡面定義一個公開方法來呼叫私有屬性)

3.3 Static(靜態)關鍵字

    靜態成員:定義時在訪問控制關鍵字後新增static關鍵字即可(訪問控制關鍵字:public. protected. private

  1. 靜態屬性用於儲存類的公有資料,可以在不同物件間共享
  2. 靜態方法裡面只能訪問靜態屬性
  3. 靜態成員不需要例項化物件就可以訪問
  4. 類的內部可以通過 self:: 或 static:: 關鍵字訪問自身靜態成員,self::$屬性 self::方法()
  5. 通過 parent:: 關鍵字訪問父類的靜態成員,也可以通過子類::父類靜態成員
  6. 通過 類名:: 的方式在類的外部訪問靜態成員

3.4 重寫和Final關鍵字

  1. 子類中編寫跟父類完全一致的方法可以完成對父類方法的重寫,方法引數最好有預設引數
  2. 對於不想被任何類繼承的類可以在class之前新增final關鍵字
  3. 對於不想被子類重寫(overwrite, 修改)的方法,可以在方法定義前面新增final關鍵字

3.5 資料訪問

  1. parent關鍵字可以可用於呼叫父類中被子類重寫了的方法
  2. self關鍵字可以用於訪問類自身的成員方法,靜態成員和類常量;不能用於訪問類自身的屬性!!!使用常量的時候不需要在常量const名稱前面新增$符號
  3. static::關鍵字用於訪問類自身定義的靜態成員,訪問靜態屬性時需要在屬性前面新增$符號。
  4. 常量屬性const不能使用物件訪問,僅能使用類訪問,在類本體內可以使用“self::常量名”,在類本體外可以使用“類名::常量名

3.6 物件介面

     介面就是把不同類的共同行為進行定義,然後在不同的類裡面實現不同的功能

  1. interface定義介面,implements用於表示類實現某個介面
  2. 接口裡面的方法沒有具體的實現,無{}
  3. 實現了某個介面的類必須提供介面中定義的方法的具體實現
  4. 不能例項化介面,但是能夠判斷某個物件是否實現了某個介面。instanceof關鍵字判斷某個物件是否實現了某個介面 $object instanceof interface
  5. 介面可以繼承介面(interface extends interface
  6. 介面中定義的所有方法必須是公有,這是介面的特性

3.7 多型

     因為介面的方法實現可以有很多,所以對於接口裡面定義的方法的具體實現是多種多樣的,這種特性我們稱為多型

     不需要知道物件屬於哪個類,只要判斷該物件的類是否實現介面,就能實現呼叫,相同程式碼實現不同結果

     形象點說就是同一個介面,不同的物件實現,得出的結果不一樣就是多型,如傳入的是人類物件,得到的是人類吃蘋果,傳入的是猴子物件,得到的就是猴子吃香蕉。相同的一行程式碼,對於傳入不同的介面的實現的物件的時候,表現是不同的。

/**
 * 多型
 * 1. 只要某個物件實現了介面(instanceof),就可以直接在物件上呼叫介面的方法
 */

interface ICanEat {
   public function eat($food);
}

// Human類實現了ICanEat介面
class Human implements ICanEat { 
  // 跟Animal類的實現是不同的
  public function eat($food){
    echo "Human eating " . $food . "\n";
  }
}

// Animal類實現了ICanEat介面
class Animal implements ICanEat {
  public function eat($food){
    echo "Animal eating " . $food . "\n";
  }
}

function eat($obj){
  if($obj instanceof ICanEat){ 
    $obj->eat("FOOD"); // 不需要知道到底是Human還是Animal,直接吃就行了
  }else{
    echo "Can't eat!\n";
  }
}

$man = new Human();
$monkey = new Animal();

// 同樣的程式碼,傳入介面的不同實現類的時候,表現不同。這就是為什麼成為多型的原因。
eat($man);
eat($monkey);

3.8 抽象類

     接口裡面的方法都是沒有實現的,而類裡面的方法都是有實現的。
     有沒有一種形態,允許類裡面一部分方法不實現呢?

  • 當介面中的某些方法對於所有的實現類都是一樣的實現方法,只有部分方法需要用到多型的特性

    • 如人和動物吃東西是不同的,但是呼吸是相同的,不需要為人和動物分別實現呼吸的功能
  1. abstract關鍵字用於定義抽象類
  2. 在抽象方法前面新增abstract關鍵字可以標明這個方法是抽象方法不需要具體實現{}
  3. 抽象類中可以包含普通的方法,有方法的具體實現
  4. 繼承抽象類的關鍵字是extends
  5. 繼承抽象類的子類需要實現抽象類中定義的抽象方法
  6. 抽象類不能被例項化,當子類繼承抽象類的時候,所有的抽象的方法都必須定義
/**
 * 抽象類
 * 1. 抽象類允許類裡面的部分方法暫時沒有具體實現,這些方法我們成為抽象方法
 * 2. 一旦類裡面有抽象方法,這個類就必須是抽象類
 * 3. 抽象類跟介面一樣,不能直接例項化為物件
 */

// 抽象類前面以abstract關鍵字開始
abstract class ACanEat {
   // 沒有實現的方法需要設定為抽象方法
   // 抽象方法需要在子類中實現 
   abstract public function eat($food);

   public function breath(){
      echo "Breath use the air.\n";
   }
}

// Human類實現了ICanEat介面
class Human extends ACanEat { 
  // 跟Animal類的實現是不同的
  public function eat($food){
    echo "Human eating " . $food . "\n";
  }
}

// Animal類實現了ICanEat介面
class Animal extends ACanEat {
  public function eat($food){
    echo "Animal eating " . $food . "\n";
  }
}

$man = new Human();
$man->eat("Apple");
$man->breath(); // 和Animal共用了抽象類ICanEat的breath方法
$monkey = new Animal();
$monkey->eat("Banana");
$monkey->breath();

四、PHP面向物件的特殊實踐

4.1 魔術方法之_toString()和invoke()

  1. __toString()當物件被當作String使用時,這個方法會被自動呼叫(需要在類中定義__tostring()方法。呼叫 echo $object
  2. __invoke()當物件被當作方法呼叫時,這個方法會被自動呼叫(需要在類中定義__invoke()方法)。呼叫 $object($parameter)
/**
 * 魔術方法1
 * 1. 當物件被當做String使用時,__toString()會被自動呼叫
 * 2. 當物件被當成方法呼叫時,__invoke()會被自動呼叫
 */
class MagicTest{
  public function __toString(){
    return "This is the Class MagicTest.\n";
  }
  public function __invoke($x){
    echo "__invoke called with parameter " . $x . "\n";
  }
}

$obj =  new MagicTest();
echo $obj;
$obj(5);

4.2 魔術方法之__call()和__callStatic()

  1. __call()方法:當物件訪問不存在的方法名稱時,此方法自動呼叫。

    • 呼叫示例:public function __call($name,$argument){}
    • 注意:訪問控制關鍵字必須為public;必須有兩個引數:物件訪問的方法名稱($name)、方法包含的引數($argument ==> 自動轉換成陣列)。
  2. __callStatic()方法:當物件訪問不存在的靜態方法名稱時,此方法自動呼叫。

    • 呼叫示例:public static function __callStatic($name,$argument){}
    • 注意:同__call();此方法為靜態方法(static)。
  3. 這兩種方法也被稱為方法的過載overloading

    • 注意區分重寫(overwrite
    • 通過這兩個方法,同一個方法的呼叫可以對應不同的方法的實現(同一個方法的靜態呼叫、動態呼叫對應不同的方法實現)
/**
 * 魔術方法之方法過載
 * 1. 當物件訪問不存在的方法名稱時,__call()方法會被自動呼叫
 * 2. 當物件訪問不存在的靜態方法名稱時,__callStatic()方法會被自動呼叫
 */
class MagicTest{
    /**
     * 自動將引數轉換成陣列
     * array (size=2)
     *  0 => string 'para1' (length=5)
     *  1 => string 'para2' (length=5)
     * @param $name
     * @param $arguments
     */
  public function __call($name, $arguments){
      var_dump($arguments);
    echo "Calling " . $name . " with parameters: " . implode(', ', $arguments) . "\n";
  }

  public static function __callStatic($name, $arguments){
    echo "Static calling " . $name . " with parameters: " . implode(', ', $arguments) . "\n";
  }
}

$obj =  new MagicTest();
$obj->runTest("para1", "para2");
MagicTest::runTest("para3","para4");

4.3 魔術方法之__get()、__set()、__isset()和__unset()

  1. 在給不可訪問屬性賦值時,__set()會被呼叫 定義function __set($name,$value)
  2. 讀取不可訪問屬性的值時,__get()會被呼叫 定義function __get($name)
  3. 當對不可訪問屬性呼叫isset()empty()時,__isset()會被呼叫
  4. 當對不可訪問的屬性呼叫unset()時,__unset()會被呼叫

     這幾個方法也被成為屬性過載的魔術方法
     所謂不可訪問屬性,實際上就是在呼叫某個屬性時發現這個屬性沒有被定義,這時候不同的操作會觸發不同的魔術方法

4.4 魔術方法之__clone()

$obj1 = $ojb; //不能實現物件複製,兩個物件變數指向同一物件
$obj1 = clone $obj; //實現物件複製,變成值相同的兩個物件

     呼叫clone時會自動呼叫__clone()方法

$james = new NbaPlayer(); //$ja0 對應記憶體地址(假設為 address0 )中儲存的是 james物件的識別符號
$james2 = clone $james;    //當希望生成一個真正獨立儲存的 NbaPlayer() 物件,但新物件的所有資料都和 $james 物件中的相同時,使用關鍵字clone

     當在class NbaPlayer()中定義了 __clone()方法 後,使用clone關鍵字時,系統將呼叫使用者定義的__clone()方法 (此時可以對clone後生成的新物件的屬性進行修改)

完!