PHP Polymorphism,多型基礎概念和範例

purple and pink hydrangeas flowers

PHP 多型是物件導向程式設計中的一個關鍵概念,允許不同類別的對象被當作同一類對象處理。這項特性促進了代碼的重用、模組化和擴展性,並在設計模式中有廣泛應用。

什麼是 PHP Polymorphism

多型的概念

多型(Polymorphism)是物件導向程式設計(Object-Oriented Programming, OOP)中的三大基石之一,這三個分別是封裝(Encapsulation)、繼承(Inheritance)和多型(Polymorphism)。

多型這個字源自希臘語,意為 “多種形態”,在程式設計的世界中,它允許我們使用一個介面或一個基礎類別來表示多種不同的實現。

多型這個詞聽起來可能很艱深,但其實它的核心觀念非常簡單。想像一下你有一把「萬能鑰匙」,這把鑰匙可以開很多種不同的鎖。

多型就是程式設計中的「萬能鑰匙」!它讓你可以用同一把鑰匙來處理很多不同類型的鎖,簡短來說,就是你可以用一個通用的方式來解決一系列相似的問題。

多型的目的

多型的主要目的是為了提供一個機制,讓一個名稱可以用於多個相關但不同類型的對象。這樣可以減少代碼的重複,並允許多個類別共享同一個介面或實現。這不僅可以提高代碼的可讀性和可維護性,也讓代碼更容易測試和重用。

使用多型有幾個明顯的優點:

代碼重用:你不必為每種特定情況寫一堆不同的代碼。一個多型的函數或方法就能搞定!

易於維護:如果你有一個功能要應用在五個不同的類別上,而這個功能以後可能還會改變。使用多型,你只需要改動一個地方的代碼,而不是五個。

靈活性:多型讓你的程式更加靈活。如果未來你想要添加或修改某些功能,你可以輕鬆地做到這一點,而不會影響到其他部分的代碼。

降低耦合:多型也有助於降低程式中各個類別或模組之間的依賴。這使得系統更為靈活,更容易進行更改和維護。

多型的實現

PHP 是一個動態類型的語言,所以它對多型的支持非常自然。在 PHP 裡,你會發現有好幾種方式來實現多型,比如透過繼承(Inheritance)和介面(Interfaces)。但不管你選擇哪一種方式,核心觀念都是一樣的:讓不同的物件可以用同一種方式來處理。

在 PHP 中,多型主要通過以下幾種方式來實現:

方法覆寫(Method Overriding):在繼承的子類別中改寫基礎類別的方法。

介面(Interface):定義一個介面,然後讓多個類別實現這個介面。

抽象類別(Abstract Class):使用抽象類別作為基礎,然後讓多個子類別繼承這個抽象類別。

PHP Polymorphism 範例

基礎範例:動物叫聲

php polymorphism class diagram example 1

考慮一個簡單的例子,我們有一個 Animal 類別,其中有一個 makeSound 方法。我們也有幾個繼承自 Animal 的子類別:DogCat

// 基礎的 Animal 類別
class Animal {
    public function makeSound() {
        echo "Some generic animal sound";
    }
}

// 繼承自 Animal 的 Dog 類別
class Dog extends Animal {
    public function makeSound() {
        echo "Woof!";
    }
}

// 繼承自 Animal 的 Cat 類別
class Cat extends Animal {
    public function makeSound() {
        echo "Meow!";
    }
}

在這個例子中,Animal 類別定義了一個 makeSound 方法,而 DogCat 類別則分別覆寫(Override)了這個方法。

// 使用多型
function animalSound(Animal $animal) {
    $animal->makeSound();
}

$dog = new Dog();
$cat = new Cat();

animalSound($dog);  // 輸出:Woof!
animalSound($cat);  // 輸出:Meow!

這裡的 animalSound 函數接受一個 Animal 類型的參數。由於 DogCat 都是 Animal 的子類,所以它們都可以作為這個函數的參數。

進階範例:形狀和面積

php polymorphism class diagram example 2

問題描述

假設我們需要計算不同形狀(如:正方形、圓形、三角形等)的面積。這些形狀都有「面積」這一共同特性,但計算面積的方式對每一種形狀都是不同的。

基礎類別和介面

首先,我們可以定義一個 Shape 介面,其中包含一個 calculateArea 方法。這個方法將用於計算形狀的面積。

interface Shape {
    public function calculateArea();
}

實現多型

接下來,我們可以建立幾個實現了 Shape 介面的類別,比如 CircleSquareTriangle

class Circle implements Shape {
    private $radius;

    public function __construct($radius) {
        $this->radius = $radius;
    }

    public function calculateArea() {
        return pi() * pow($this->radius, 2);
    }
}

class Square implements Shape {
    private $sideLength;

    public function __construct($sideLength) {
        $this->sideLength = $sideLength;
    }

    public function calculateArea() {
        return pow($this->sideLength, 2);
    }
}

class Triangle implements Shape {
    private $base;
    private $height;

    public function __construct($base, $height) {
        $this->base = $base;
        $this->height = $height;
    }

    public function calculateArea() {
        return 0.5 * $this->base * $this->height;
    }
}

使用多型

有了這些類別後,我們可以創建一個函數來計算任何形狀的面積,而不需要知道它實際上是什麼形狀。

function calculateShapeArea(Shape $shape) {
    return $shape->calculateArea();
}

$circle = new Circle(5);
$square = new Square(4);
$triangle = new Triangle(3, 4);

echo "Circle area: " . calculateShapeArea($circle) . "\n";  // 輸出:Circle area: 78.539816339745
echo "Square area: " . calculateShapeArea($square) . "\n";  // 輸出:Square area: 16
echo "Triangle area: " . calculateShapeArea($triangle);     // 輸出:Triangle area: 6

在這個例子中,calculateShapeArea 函數接受一個實現了 Shape 介面的對象作為參數。由於 CircleSquareTriangle 都實現了這個介面,它們都可以作為這個函數的參數。

這種方式增加了代碼的可擴展性和可維護性。假如未來我們要加入更多種形狀,只需要讓新的形狀類別實現 Shape 介面,而不需要修改 calculateShapeArea 函數。

實用範例:資料庫操作

php polymorphism class diagram example 3

問題描述

在大型應用程式中,我們可能會遇到需要與多種資料庫互動的情況,例如 MySQL、PostgreSQL、MongoDB 等。每種資料庫的操作方法都有些許不同,但基本的 CRUD(Create、Read、Update、Delete)操作是類似的。

基礎介面

我們首先定義一個 Database 介面,其中包括基本的 CRUD 方法。

interface Database {
    public function connect();
    public function create($table, $data);
    public function read($table, $id);
    public function update($table, $data, $id);
    public function delete($table, $id);
}

實現多型

接著,我們可以為不同的資料庫(如 MySQL 和 MongoDB)建立實現了 Database 介面的類別。

// MySQL 的實現
class MySQLDatabase implements Database {
    public function connect() {
        // MySQL 連接邏輯
    }

    public function create($table, $data) {
        // MySQL 新增資料邏輯
    }

    public function read($table, $id) {
        // MySQL 讀取資料邏輯
    }

    public function update($table, $data, $id) {
        // MySQL 更新資料邏輯
    }

    public function delete($table, $id) {
        // MySQL 刪除資料邏輯
    }
}

// MongoDB 的實現
class MongoDBDatabase implements Database {
    public function connect() {
        // MongoDB 連接邏輯
    }

    public function create($table, $data) {
        // MongoDB 新增資料邏輯
    }

    public function read($table, $id) {
        // MongoDB 讀取資料邏輯
    }

    public function update($table, $data, $id) {
        // MongoDB 更新資料邏輯
    }

    public function delete($table, $id) {
        // MongoDB 刪除資料邏輯
    }
}

使用多型

有了這些類別後,我們可以編寫一個通用的資料操作函數,該函數可以接受任何實現了 Database 介面的對象。

function performDatabaseOperation(Database $db, $operation, $table, $data = null, $id = null) {
    switch ($operation) {
        case 'create':
            $db->create($table, $data);
            break;
        case 'read':
            return $db->read($table, $id);
        case 'update':
            $db->update($table, $data, $id);
            break;
        case 'delete':
            $db->delete($table, $id);
            break;
    }
}

$mysqlDB = new MySQLDatabase();
$mongoDB = new MongoDBDatabase();

// 使用 MySQL 進行新增操作
performDatabaseOperation($mysqlDB, 'create', 'users', ['name' => 'John', 'email' => 'john@email.com']);

// 使用 MongoDB 進行讀取操作
$userData = performDatabaseOperation($mongoDB, 'read', 'users', null, 'user_id_123');

這個設計使得我們可以靈活地在不同的資料庫之間切換,而不需要對資料操作函數進行任何修改。新增支持新的資料庫也變得相對容易,只需建立一個新的類別實現 Database 介面即可。


更所參考資源

  1. PHP: The Right Way
  2. Polymorphism – explain the polymorphism concept and show you how to implement polymorphism in PHP using abstract classes or interfaces.

發佈留言

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