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