PHP_Object_Interfaces

在編寫程序的時候我常常陷入糾結,一個抽象對象,到底應該定義成 抽象類(Abstract Class) 還是 接口(Interface) 呢?二者具有很大的相似性,甚至可以相互替換,難以選擇。在 Stackoverflow 上這個問題被問了很多次,各種編程語言的都有。而在 PHP 官網文檔 Abstract ClassInterface 章節下面的評論里,人們也是爭論不休。為了弄明白這個問題,必須仔細對比一下二者的區別和使用場景。

一、Abstract Class 與 Interface 的構造

抽象類 Abstract Class

<?php
abstract class A {
    abstract public function method1();
    abstract public function method2();
    public function method3() {
        //... code ...
    }
}
?>

接口 Interface

<?php
interface B {
    public function method4();
    public function method5();
}
?>

可以看到,Abstract Class 中可以有抽象函數(method1,method2),也可以有具體的函數實現(method3),而 Interface 中只能定義函數而不能有實現(method4,method5)。顯然,如果一個抽象類中的函數全部是抽象函數,那麼這個抽象類就退化成了接口。不過要注意的是

  • PHP 和 Java 一樣,一個 Class 只能繼承一個 Abstract Class,但可以實現多個 Interface。這即是所謂的單一繼承體系,也就是子類別只能繼承一個父類別;一個父類別則可以被多個子類別所繼承。據說在 Python 中是可以多重繼承的。
  • 在 Abstract Class 中可以聲明屬性成員變量(attribute),而 Interface 不可以。

二、舉例

下面的例子可以幫助我們從另一個層面:Abstract Class 和 Interface 所反映出的設計理念,來分析一下二者的本質區別。

假設我們要定義一個關於“門”的 Class,門有“開”和“關”兩個動作。此時我們既可以用 Abstract Class 也可以用 Interface 來描述這個抽象概念——

抽象類 Abstract Class

<?php
abstract class Door {
    abstract public function open();
    abstract public function close();
}
?>

接口 Interface

<?php
interface Door {
    public function open();
    public function close();
}
?>

在 Abstract Class 中並沒有實現開門和關門方法,因為有的門可以用鑰匙打開,有的門用密碼打開,還有的門可以用指紋打開,這些具體方法就交給子類繼承去實現。在這樣看上去 Abstract Class 和 Interface 二者的使用沒有太大差別。

現在如果定義一種具有“報警”功能的門,Abstract Class 和 Interface 描述類似——

抽象類 Abstract Class

<?php
abstract class Door {
    abstract public function open();
    abstract public function close();
    abstract public function alarm();
}
class AlarmDoor extends Door {
    public function open() {
        //... 
    }
    public function close() {
        //... 
    }
    public function alarm() {
        //... 
    }
}
?>

接口 Interface

<?php
interface Door
{
    public function open();
    public function close();
    public function alerm();
}
class AlarmDoor implements Door {
    public function open() {
        //... 
    }
    public function close() {
        //... 
    }
    public function alarm() {
        //... 
    }
}
?>

很明顯上面的做法是不對的。它在定義中把 Door 本身固有的開門、關門的行為方法和另外一個概念“報警器”的行為方法混在了一起。有的門可以報警,而有的門可能沒有報警功能;而會報警的門也可能多種多樣(鑰匙開門,刷卡開門等等)。我們必須將報警行為單獨定義到另一個對象中,那麼就有 3 種方式:

  • 門和報警器都用 abstract class 方式定義;
  • 門和報警器都用 interface 方式定義;
  • 一個使用 abstract class 方式定義,另一個使用 interface 方式定義。

由於 PHP 中子類只可繼承自一個 Abstract Class,而且 Abstract Class 不支持多重繼承(abstract class cannot extend abstract class),所以第一種方式顯然不行。而第二種方式則沒有能夠正確的揭示我們的設計意圖,也就是沒有反映出 AlarmDoor 在概念本質上是 Door,同時它有具有報警功能。所以對於 Door 這個概念,我們應該採取 Abstract Class 方式來定義,AlarmDoor 繼承自 Door,而報警概念通過 Interface 方式定義。

<?php
abstract class Door {
    abstract function open();
    abstract function close()}
interface Alarm {
    function alarm();
}
class AlarmDoor extends Door implements Alarm {
    public function open() {
        //... 
    }
    public function close() {
        //... 
    }
    public function alarm() {
        //... 
    }
}
?>

這樣的實現基本上正確地反映了我們的設計意圖。接口是可以多重繼承的,子類也可以實現多個接口。這樣就使得我們可以繼續擴展門的功能,比如給門再裝個攝像頭……

三、結論

當我們使用 Abstract Class 的時候,我們應該定義一類對象的屬性,即描述它 是什麼。而 Interface 則類似於插件,側重於提供附加的能力,約定它 能做什麼

參考鏈接:
該用 Abstract Class 還是 Interface?
深入理解abstract class和interface