PHP 物件繼承 (Object Inheritance),提升你的程式結構與設計

man, woman and child holding hands on seashore

為什麼要使用 PHP 物件繼承

使用 PHP 物件繼承(Object Inheritance)有多種原因和好處,以下列舉其中幾個:

代碼重用

物件繼承允許你重用已經寫好的代碼。如果你有一個類別(例如「動物」),然後需要創建與它相似但有一些特殊功能的新類別(例如「哺乳動物」、「鳥類」),你可以通過繼承來實現,而不需要重新編寫相同的代碼。

維護性

使用繼承可以讓代碼更容易維護。當基礎類別(Parent Class)進行更新或修改時,所有繼承自它的子類別(Subclasses)也會自動獲得這些更改,這樣就能減少維護的工作量。

組織化

繼承有助於更好地組織和分層你的代碼。它讓你可以用更高層次的類別來表示共同的特性,然後通過子類別來表示更具體的行為和屬性。

多型

繼承是多型(一個對象能以多種形式存在)的基礎。在子類別中,你可以重寫(Override)父類別中的方法,使得子類別能有自己獨特的行為,同時還保留父類別的共同特性。

擴展性

如果你的應用程序需要新增功能或特性,使用物件繼承可以讓這個過程更為簡單。你只需創建一個新的子類別即可,而不是修改已經存在的代碼。

封裝

繼承也能促進封裝,因為它允許子類別繼承父類別的屬性和方法,而不需要知道這些屬性和方法是如何實現的。

使用物件繼承是物件導向程式設計(OOP)中一個非常重要的概念,它能提供以上所述的諸多好處。然而,也要注意不要過度使用繼承,因為這有時可能導致代碼變得過於複雜。

繼承的目的

但要特別注意,物件繼承的主要目的並不是程式碼重用,而是抽象化設計,如果繼承只是為了重複使用程式碼,最好重新思考一下。

什麼是 PHP 物件繼承

物件導向程式設計(Object-Oriented Programming, OOP)中的「繼承」(Inheritance)是一種讓新類別(子類別)可以使用已存在的類別(父類別)的屬性和方法的機制。簡單來說,繼承允許我們建立一個新類別,這個新類別會「繼承」或「承接」既有類別的特性,並可加以擴展或修改。

或者想像一下,你有一個玩具箱,裡面有各種不同的積木。有一天,你想建造一個新的機器人積木。你發現,之前已經有一個基本的機器人積木,它有手、有腳,還會走路和說話。

現在,你想讓新的機器人積木有更多功能,比如飛行。你不需要從頭開始做一個全新的機器人,你可以拿之前的機器人來加上翅膀。這樣,新的機器人就會有手、腳、走路、說話和飛行的能力!

在程式裡面,這就像是你有一個「動物」的程式碼,它會吃東西和走路。然後你想做一個「狗」的程式碼,狗除了會吃和走,還會汪汪叫。你就可以用「繼承」,讓「狗」的程式碼繼承「動物」的程式碼,這樣「狗」就自動會吃東西和走路,你只需要加上汪汪叫的部分。

所以「繼承」就是一種讓新的東西可以用舊的東西的功能,再加上新功能的方法。

實際範例

範例:動物與狗

舉例來說,假設我們有一個名為「動物」(Animal)的類別,其中有「吃」(eat)和「走」(walk)的方法。然後,我們想要建立一個新的類別名為「狗」(Dog)。狗是一種動物,所以它應該有吃和走的能力。在這種情況下,我們可以讓「狗」類別繼承「動物」類別,這樣「狗」類別就會自動擁有「吃」和「走」的方法,無需重新編寫這些方法。

在 PHP 中,我們使用 extends 關鍵字來實現繼承:

class Animal {
  public function eat() {
    echo "This animal eats food.";
  }
  public function walk() {
    echo "This animal walks.";
  }
}

class Dog extends Animal {
  public function bark() {
    echo "The dog barks.";
  }
}

在這個例子中,Dog 類別繼承了 Animal 類別。所以,Dog 類別不僅有一個自己獨特的 bark() 方法,還繼承了 Animal 類別的 eat()walk() 方法。所以你可以創建一個 Dog 類別的實例,並呼叫所有這些方法:

$myDog = new Dog();
$myDog->eat();  // Output: "This animal eats food."
$myDog->walk(); // Output: "This animal walks."
$myDog->bark(); // Output: "The dog barks."

範例:新口味餅乾

假設我們有一個基本的「餅乾」(Cookie)類別,這個類別有一個方法叫做「taste」(味道)。這個方法會告訴我們這個餅乾是「酥脆」的。

class Cookie {
  public function taste() {
    echo "這個餅乾是酥脆的!";
  }
}

現在,我們想創建一個新類別叫做「ChocolateCookie」(巧克力餅乾)。我們想讓這個巧克力餅乾保有基本餅乾的「酥脆」特性,但還要加上「有巧克力味」。

為了達到這個目的,我們會用到「繼承」。我們讓「ChocolateCookie」類別繼承「Cookie」類別:

class ChocolateCookie extends Cookie {
  public function chocolateTaste() {
    echo "這個餅乾有巧克力味!";
  }
}

在這個範例中,「ChocolateCookie」類別使用了 extends 關鍵字來繼承「Cookie」類別。所以,「ChocolateCookie」自動獲得了「Cookie」類別的 taste() 方法。

現在我們可以創建一個「ChocolateCookie」的實例,並呼叫這些方法:

$myChocolateCookie = new ChocolateCookie();
$myChocolateCookie->taste();        // Output: "這個餅乾是酥脆的!"
$myChocolateCookie->chocolateTaste(); // Output: "這個餅乾有巧克力味!"

如你所見,我們的「ChocolateCookie」不只有巧克力的味道,還繼承了基本餅乾的「酥脆」特性!這就是「繼承」如何在程式中工作的一個範例。

範例:大學課程

假設我們有一個基本的「課程」(Course)類別。這個類別有基本的屬性,如「課程名稱」和「教授名稱」,以及一個方法叫做 getDetails(),這個方法會列出課程的基本資訊。

// 基礎的 "Course" 類別
class Course {
  // 定義屬性:課程名稱和教授名稱
  public $courseName;
  public $professorName;

  // 建構函數:初始化課程名稱和教授名稱
  public function __construct($courseName, $professorName) {
    $this->courseName = $courseName;
    $this->professorName = $professorName;
  }

  // 方法:獲取課程的基本資訊
  public function getDetails() {
    echo "這門課是 {$this->courseName},由 {$this->professorName} 教授。";
  }
}

現在,假設我們想要創建一個更具體的「程式設計課程」(ProgrammingCourse)類別。我們想要這個新類別除了有基本課程的所有資訊之外,還要有「使用的程式語言」這個新的屬性。

我們可以讓「ProgrammingCourse」類別繼承「Course」類別,並加入新的屬性:

// "ProgrammingCourse" 類別繼承自 "Course" 類別
class ProgrammingCourse extends Course {
  // 新增屬性:使用的程式語言
  public $languageUsed;

  // 建構函數:初始化課程名稱、教授名稱,以及使用的程式語言
  public function __construct($courseName, $professorName, $languageUsed) {
    // 使用 parent 關鍵字呼叫父類別的建構函數
    parent::__construct($courseName, $professorName);
    // 初始化新屬性
    $this->languageUsed = $languageUsed;
  }

  // 方法:獲取課程的詳細資訊,包括使用的程式語言
  public function getDetails() {
    // 使用 parent 關鍵字呼叫父類別的 getDetails 方法
    parent::getDetails();
    // 添加新的資訊
    echo " 使用的程式語言是 {$this->languageUsed}。";
  }
}

在這個例子中,ProgrammingCourse 使用了 extends 關鍵字來繼承 Course 類別。因此,它繼承了父類別的所有屬性和方法。此外,我們還對 getDetails() 方法進行了擴展,讓它能顯示新的「使用的程式語言」屬性。

我們可以這樣使用這個新類別:

// 創建一個 "ProgrammingCourse" 的實例
$myCourse = new ProgrammingCourse("資料結構", "張教授", "Python");
// 呼叫 getDetails 方法,顯示詳細資訊
$myCourse->getDetails();  // 輸出: "這門課是 資料結構,由 張教授 教授。 使用的程式語言是 Python。"

這範例展示了如何從一個基礎類別(Course)繼承,然後添加額外的特性(ProgrammingCourse)。

範例:汽車與電動車

這次讓我們以「車輛」(Vehicle)和「電動車」(ElectricCar)為例,來談談「方法覆寫(Override Method)」這一概念。

首先,我們建立一個基礎的「車輛」(Vehicle)類別。這個類別有一個方法叫做 fuelType(),用來描述車輛使用的燃料類型。

// 定義一個基礎的 "Vehicle" 類別
class Vehicle {
  // 方法:描述車輛使用的燃料類型
  public function fuelType() {
    echo "這輛車使用汽油。";
  }
}

接著,我們創建一個「電動車」(ElectricCar)類別,這個類別繼承自「車輛」(Vehicle)類別。因為電動車的燃料類型和一般車輛不同,我們需要覆寫 fuelType() 方法。

// "ElectricCar" 類別繼承自 "Vehicle" 類別
class ElectricCar extends Vehicle {
  // 方法覆寫:描述電動車使用的燃料類型
  public function fuelType() {
    echo "這輛車使用電能。";
  }
}

在這個例子中,ElectricCar 類別使用了 extends 關鍵字來繼承 Vehicle 類別。然後,我們覆寫了 fuelType() 方法,使其輸出「這輛車使用電能」,以反映電動車的特性。

現在,讓我們看看如何使用這兩個類別:

// 創建一個 "Vehicle" 的實例
$myVehicle = new Vehicle();
$myVehicle->fuelType();  // 輸出: "這輛車使用汽油。"

// 創建一個 "ElectricCar" 的實例
$myElectricCar = new ElectricCar();
$myElectricCar->fuelType();  // 輸出: "這輛車使用電能。"

這個例子展示了如何透過「方法覆寫(Override Method)」來改變繼承自父類別的方法。當我們呼叫 fuelType() 方法時,ElectricCar 會使用自己覆寫後的版本,而不是使用 Vehicle 的原始版本。

範例:人和學生

這次我們用「人」(Person)和「學生」(Student)的例子來說明如何在子類別中呼叫父類別的建構子(constructor)。

首先,我們有一個「人」(Person)類別,其中有一個建構子用於設定「名字」(name)和「年齡」(age)。

// 定義一個 "Person" 類別
class Person {
  public $name;
  public $age;

  // 建構子:設定名字和年齡
  public function __construct($name, $age) {
    $this->name = $name;
    $this->age = $age;
  }
}

接著,我們建立一個「學生」(Student)類別,這個類別繼承自「人」(Person)類別。學生除了有名字和年齡,還有「學校」(school)。

我們會在這個子類別的建構子中呼叫父類別的建構子,使用 parent::__construct() 來初始化名字和年齡。

// "Student" 類別繼承自 "Person" 類別
class Student extends Person {
  public $school;

  // 建構子:設定名字、年齡,以及學校
  public function __construct($name, $age, $school) {
    // 呼叫父類別的建構子來設定名字和年齡
    parent::__construct($name, $age);
    // 設定學校
    $this->school = $school;
  }
}

最後,我們來看如何使用這些類別:

// 創建一個 "Student" 的實例
$myStudent = new Student("小明", 20, "某大學");

// 輸出該實例的資料
echo "名字:{$myStudent->name}, 年齡:{$myStudent->age}, 學校:{$myStudent->school}";
// 輸出:名字:小明, 年齡:20, 學校:某大學

這個例子展示了如何在子類別(Student)的建構子中,透過 parent::__construct() 呼叫父類別(Person)的建構子。這樣可以確保父類別的屬性(名字和年齡)也得到適當的初始化。

這種做法讓我們可以在不修改父類別代碼的情況下,為子類別添加新的屬性(學校)和功能。

最後

通過以上範例,你已不知不覺學到了幾個物件關鍵知識。

繼承(Inheritance)

學習如何使用 extends 關鍵字來創建一個新的子類別(subclass)。

方法覆寫(Method Overriding)

了解如何在子類別中覆寫(override)來自父類別(parent class)的方法。

父類別建構子的呼叫(Parent Constructor Invocation)

學習如何使用 parent::__construct() 來呼叫父類別的建構子(constructor)。

封裝(Encapsulation)

學習如何封裝代碼,使其更安全、更容易維護。

代碼組織(Code Organization)

通過使用繼承,理解如何更有效地組織和模組化你的代碼。

物件繼承的基礎知識,對之後的多型應用至關重要,熟悉繼承之後再來學習多型就簡單啦。


更多參考文章:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *