使用接口(interface),可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容。
接口是通過?interface?關鍵字來定義的,就像定義一個標準的類一樣,但其中定義所有的方法都是空的。
接口中定義的所有方法都必須是public
(公有),這是接口的特性。
什么時候使用接口?
- 因為實現了同一個接口,所以開發者創建的對象雖然源自不同的類,但可能可以交換使用。 常用于多個數據庫的服務訪問、多個支付網關、不同的緩存策略等。 可能不需要任何代碼修改,就能切換不同的實現方式。
- 能夠讓函數與方法接受一個符合接口的參數,而不需要關心對象如何做、如何實現。 這些接口常常命名成類似?
Iterable
、Cacheable
、Renderable
, 以便于體現出功能的含義。
常量
接口中也可以定義常量。接口常量和類常量的使用完全相同, 在 PHP 8.1.0 之前 不能被子類或子接口所覆蓋。
接口的實現
要實現一個接口,使用?implements
?操作符。類中必須實現接口中定義的所有方法,否則會報一個致命錯誤。
類可以實現多個接口,用逗號來分隔多個接口的名稱。
<?php
// 聲明一個'iTemplate'接口
interface iTemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 實現接口
class Template implements iTemplate
{
private $vars = array();
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
特征:
- 可以指定某個類必須實現哪些方法,但不需要定義這些方法的具體內容。
- 就像定義一個標準的類一樣,但其中定義所有的方法都是空的。
- 接口中定義的所有方法都必須是
public
(公有),這是接口的特性。 - 類中必須實現接口中定義的所有方法,否則會報一個致命錯誤。
- 類可以實現多個接口,用逗號來分隔多個接口的名稱。
- 類要實現接口,必須使用和接口中所定義的方法完全一致的方式。否則會導致致命錯誤
- 接口也可以繼承,可以通過?extends?操作符擴展。讓一個接口繼承另一個接口,即常用的繼承(擴展新抽象方法),無覆蓋的關系
- 接口中也可以定義常量。接口常量和類常量的使用完全相同,但是不能被子類或子接口所覆蓋
- 接口中的成員屬性,必須是常量(不能有變量)
- 可以使用一個類來實現接口中全部方法,也可以使用一個抽象類,來實現接口中的部分方法
- 一個類可以在繼承另一個類的同時,使用implements實現一個接口,也可以實現多個接口(一定要先繼承,再實現接口)
注意:
- 由于接口(interface)和類(class)、trait 共享了命名空間,所以它們不能重名。
- 接口中的方法,必須全部是抽象方法,所以接口中的抽象方法不需要使用abstract關鍵字,直接用分號結束即可
- 接口可以定義魔術方法,以便要求類(class)實現這些方法。
- 雖然沒有禁止,但是強烈建議不要在接口中使用?構造器。 因為這樣在對象實現接口時,會大幅降低靈活性。 此外,也不能強制確保構造器遵守繼承規則,將導致不可預料的行為結果。
- 類實現接口時,必須以兼容的簽名定義接口中所有方法。
- 接口加上類型約束,提供了一種很好的方式來確保某個對象包含有某些方法。參見?instanceof?操作符和類型聲明。
面向接口開發
接口,實際上也可以看做是一種契約。我們經常會拿電腦主機箱后面的插口來說明。比如USB接口,我們定義了它的大小,里面的線路格式,不管你插進來的是什么,我們都可以連通。而具體的實現則是取決于電腦軟件對插入的硬件的解釋,比如U盤就會去讀取它里面的內容,而鍵盤則會識別為一個外設。
從這里可以看出,接口能夠為我們程序的擴展提供非常強大的支撐。任何面向對象語言中接口都是非常重要的特性。
可擴充(繼承?)的接口
<?php
interface A
{
public function foo();
}
interface B extends A
{
public function baz(Baz $baz);
}
// 正確寫法
class C implements B
{
public function foo()
{
}
public function baz(Baz $baz)
{
}
}
// 錯誤寫法會導致一個致命錯誤
class D implements B
{
public function foo()
{
}
//無法檢查 D::baz(Foo $foo) 和 B::baz(Baz $baz) 之間的兼容性,因為 Baz 類在代碼中不可用
//未實現接口B的方法
public function baz(Foo $foo)
{
}
}
?>
擴展多個接口
<?php
interface A
{
public function foo();
}
interface B
{
public function bar();
}
interface C extends A, B
{
public function baz();
}
class D implements C
{
public function foo()
{
}
public function bar()
{
}
public function baz()
{
}
}
?>
使用接口常量
<?php
interface A
{
const B = 'Interface constant';
}
// 輸出接口常量
echo A::B;
// 錯誤寫法,因為常量不能被覆蓋。接口常量的概念和類常量是一樣的。
class B implements A
{
const B = 'Class constant';
}
// 輸出: Class constant
// 在 PHP 8.1.0 之前,不能正常運行
// 因為之前還不允許覆蓋類常量。
echo B::B;
?>
抽象(abstract)類的接口使用
<?php
interface A
{
public function foo(string $s): string;
public function bar(int $i): int;
}
// 抽象類可能僅實現了接口的一部分。
// 擴展該抽象類時必須實現剩余部分。
abstract class B implements A
{
public function foo(string $s): string
{
return $s . PHP_EOL;
}
}
class C extends B
{
public function bar(int $i): int
{
return $i * 2;
}
}
?>
同時使用擴展和實現
<?php
class One
{
/* ... */
}
interface Usable
{
/* ... */
}
interface Updatable
{
/* ... */
}
// 關鍵詞順序至關重要: 'extends' 必須在前面
class Two extends One implements Usable, Updatable
{
/* ... */
}
?>