摘要訊息 : 學習 PHP 中的介面.

0. 前言

假設現在有一個 PHP 需求 : 有兩個完全不同的類別, 但是這兩個類別 A、B 都有一個共同名稱的方法 C, 只是方法的實作方式不同. 現在有一個函式, 可能呼叫類別 A 的 C 方法, 也可能呼叫 B 的 C 方法. 如果要去實現這個函式, 那麼可能需要寫兩個不同的函式. 但是如果我想要簡單地用一個函式去實現, 應該如何做呢? 於是, 這就用到了 PHP 的一個特性 - 介面.

正式介紹之前, 我們要用一個例子, 將抽象的介面模型化, 這也就是之前介紹過的編程模型, 這有利於任何人去理解介面.

現在我們要生產一部手機. 那麼, 顯示器、電池、CPU、記憶體等組成了一部手機. 一部手機必須要有這些零件, 否則將無法組成一部手機. 但是這些零件可以各有不同, 相互毫無關係. 例如顯示器, 它可以是任何供應商以不同的生產方式生產的, 只要在兼容並蓄的情況下, 它可以是任何手機的顯示器. 反之來說, 任何手機都可以用這個顯示器作為零件. 如果將這個模型轉化為程式設計, 那麼手機就是一個類別, 那些顯示器、電池、CPU 等的零部件就是類別內的方法, 如果不能實現這些方法, 或者沒有這些方法, 將無法組成這部手機, 這些方法抽象化後被定義為介面. 方法的實作方式可以不同 (也就是生產方法和生產廠商可以是不同的), 但是最後都被用於實現一部手機 (類別被實例化), 不管手機是什麼樣子的, 有什麼功能. 換句話說, 任何手機都可以用顯示器這個方法實現一些功能 (介面的呼叫).

如果你已經對介面有了概念上的了解, 那麼可以正式開始學習並且使用介面了.

本文於 2022 年 3 月 18 日進行一次更新和修正. 修正之後本文已經歸檔, 不再享受更新.

1. 介面基礎

介面在 PHP 中有兩個關鍵字 : interfaceimplements. 它的宣告方式和類別是相似的. 只是類別使用 class 來宣告, 介面使用 interface 來宣告. 介面可以擁有常數成員, 抽象方法 :

<?php
interface itf {
    const BUFFER = 2048;
    public function func();
}

類別支援實作多個介面 :

<?php
interface itf {}
interface itf_2 {}
class foo implements itf, itf_2 {}

介面支援從另外一個介面衍生 :

<?php
interface itf {}
interface itf_ext extends itf {}

但是我們不可以在介面中宣告屬性成員. 介面也不可以被實例化. 介面中的方法不可以被實作, 只能是抽象的方法.

關於介面的介紹, 就到這裡, 下面我們通過一個實例來學習使用介面.

這個實例中, 實現了 people 介面, people 類別, teacher 類別和 student 類別. 其中, people, teacherstudent 類別都是以 people 介面為基礎, 並且 teacher 類別和 student 類別都是 people 類別的衍生 :

<?php
interface people_interface {
    public function insert_name($name);
    public function insert_gender($gender);
    public function get_name();
    public function get_gender();
}
class people implements people_interface {
    protected $name;
    protected $gender;
    public function insert_name($name) {
        $this->name = $name;
    }
    public function insert_gender($gender) {
        $this->gender = $gender;
    }
    public function get_name() {
        return $this->name;
    }
    public function get_gender() {
        return $this->gender;
    }
}
class student extends people implements people_interface {
    protected $id;
    private $data;
    public function insert_id($id) {
        $this->id = $id;
    }
    public function get_id() {
        return $this->id;
    }
    public function insert(people_interface $people) {
        $this->data = $people->get_name()." : ".$people->get_gender();
    }
    public function get_data() {
        return $this->data;
    }
}
class teacher extends people implements people_interface {
    protected $subject;
    public function get_name() {
        return $this->name." : ".$this->subject;
    }
    public function insert_subject($subject) {
        $this->subject = $subject;
    }
    public function get_subject() {
        return $this->subject;
    }
}
$t = new teacher();
$t->insert_name("Jack");
$t->insert_subject("math");
$t->insert_gender("F");
$p = new people();
$p->insert_name("Amy");
$p->insert_gender("M");
$s = new student();
$s->insert($teacher);
echo $s->getData();
echo "\n";
$s->insert($people);
echo $s->get_data();
/* 結果 :
    Jack : math : F
    Amy : M
*/

從上面這個實例可以看到, 介面中的抽象方法必須在類別中被實作, 否則就會出現錯誤.

我們特別注意到 student 類別中的 insert, 它的引數中有一個 people_interface, 也就是 people 的介面. 我們看到實際在呼叫 studentinsert 的時候, 引數是一個類別. 這個方法被呼叫了兩次, 第一次放入的是 $t, 第二次放入的是 $p. 實際 insert 方法裡面呼叫了介面中存在的兩個抽象方法, get_nameget_gender 方法. 也就是說, 只要一個類別使用了 people_interface 介面, 那麼就可以成為 insert 方法的引數.

介面的實際用途是很難用言語去表達清楚的, 如果你不是很明白的話, 建議你看完上面這段之後, 重新回到第一段去看看那個疑問. 這之後, 你再回來看看這段話, 也許你就會有所感悟.

總而言之, 使用介面可以讓 PHP 編程更加靈活, 也可以減少一些重複的代碼. 而且, 你可能還可以通過介面委託他人幫你實現一些功能. 使用介面之後, 別人只要知道如何使用介面, 就可以無縫地使用你的程式碼.