我并不是說(shuō)接口“改變了類(lèi)的行為”,而是說(shuō)接口使擴(kuò)展類(lèi)功能變得容易。
要理解接口,作為一個(gè)面向?qū)ο蟮木幊谈拍?,我們首先?yīng)該理解它要解決什么問(wèn)題。
接口是一種契約。這是在 PHP 中實(shí)現(xiàn) duck-typing 的方法。您需要從庫(kù)編寫(xiě)者的角度思考,他希望向其他人公開(kāi)功能。例如,
class Greeter { public function greet($person) { echo "Hello, {$person->getName()}!\n"; } }
為了確保庫(kù)用戶知道 $person
需要有 getName()
方法,您可以創(chuàng)建一個(gè)類(lèi) Person
> 有一個(gè) getName()
方法。然后使用 類(lèi)型聲明 來(lái)檢測(cè)代碼執(zhí)行時(shí)的潛在錯(cuò)誤已解析。
class Greeter { public function greet(Person $person) { echo "Hello, {$person->getName()}!\n"; } } class Person { public string $name; public function getName(): string { return $this->name; } }
假設(shè)有另一個(gè)庫(kù)可以用食物來(lái)喂養(yǎng)東西:
class Feeder { public function feed(Eater $eater, string $food) { $eater->eat($food); } } class Animal { private $stomach = []; public function eat(string $food) { $stomach = $food; } }
現(xiàn)在,假設(shè)用戶想要編寫(xiě)一個(gè)既可以吃飯又可以打招呼的 Pet
類(lèi)。用戶不想僅僅為了 Pet
再次編寫(xiě)這些功能。
如何編寫(xiě) Pet
以便同時(shí)使用 Greeter
和 Feeder
庫(kù)?
也許是這樣的?
class Pet extends Person, Animal { }
不幸的是,PHP 不支持多重繼承。一個(gè)類(lèi)只能擴(kuò)展
一個(gè)類(lèi)。上面的代碼無(wú)效。所以在目前的情況下,用戶只能使用其中一個(gè)庫(kù)。
此外,對(duì)于不同的事物,“名稱(chēng)”可能是一個(gè)非常不同的概念(例如,一個(gè)人可能會(huì)使用 getName() 返回
$first_name
和 $last_name
代碼>)。您的庫(kù)類(lèi)中可能沒(méi)有合理的默認(rèn)實(shí)現(xiàn) getName()
方法。
所以,作為一名庫(kù)編寫(xiě)者,您希望他/她的庫(kù)對(duì)用戶盡可能靈活。你能做什么?
接口是方法簽名的聲明。這是在沒(méi)有具體類(lèi)/繼承要求的情況下聲明庫(kù)要求的快捷方式。
使用接口,您可以像這樣重寫(xiě)兩個(gè)庫(kù):
Greeter
庫(kù)class Greeter { public function greet(Namer $namer) { echo "Hello, {$namer->getName()}!\n"; } } interface Namer { public function getName(): string; }
Feeder
庫(kù)class Feeder { public function feed(Eater $eater, string $food) { $eater->eat($food); } } interface Eater { public function eat(string $food); }
不需要一個(gè)具體的類(lèi)(或父類(lèi)繼承),一個(gè)類(lèi)可以實(shí)現(xiàn)多個(gè)接口。所以下面的 Pet
類(lèi)在 PHP 中是完全有效的:
class Pet implements Namer, Eater { private array $stomach = []; private string $name = ''; public function __construct(string $name) { $this->name = $name; } /** * Implements Namer. */ public function getName(): string { return $this->name; } /** * Implements Eater. */ public function eat(string $food) { $this->stomach[] = $food; } } $greeter = new Greeter(); $feeder = new Feeder(); $pet = new Pet('Paul'); $greeter->greet($pet); $feeder->feed($pet, 'a biscuit');
現(xiàn)在,此 Pet
類(lèi)的對(duì)象可以與 Greeter
庫(kù)和 Feeder
庫(kù)一起使用。
ArrayAccess
接口怎么樣?ArrayAccess 接口不是由第三方聲明的接口庫(kù)編寫(xiě)者,但由核心 PHP 編寫(xiě)者編寫(xiě)。核心PHP編寫(xiě)器對(duì)此提供了更深刻的支持。
有點(diǎn)像我們之前提到的接口,PHP 為實(shí)現(xiàn)它的類(lèi)提供功能。但核心 PHP 不是提供上面的 Greeter
或 Feeder
示例,而是提供 實(shí)現(xiàn) ArrayAccess 的類(lèi)的語(yǔ)法糖。這意味著您可以使用更簡(jiǎn)潔的代碼來(lái)處理實(shí)現(xiàn) AccessAccess 接口的類(lèi)。
在官方示例中,
<?php class Obj implements ArrayAccess { private $container = array(); public function __construct() { $this->container = array( "one" => 1, "two" => 2, "three" => 3, ); } public function offsetSet($offset, $value) { if (is_null($offset)) { $this->container[] = $value; } else { $this->container[$offset] = $value; } } public function offsetExists($offset) { return isset($this->container[$offset]); } public function offsetUnset($offset) { unset($this->container[$offset]); } public function offsetGet($offset) { return isset($this->container[$offset]) ? $this->container[$offset] : null; } }
如果您實(shí)現(xiàn)了它們,則代替這些:
$obj = new Obj; $obj->offsetSet(10, "hello"); $obj->offsetSet(11, "world"); if ($obj->offsetUnset(12)) { $obj->offsetUnset(12); } echo $obj->offsetGet(11);
您可以將 $obj
與類(lèi)似數(shù)組的語(yǔ)法一起使用,以使代碼更短:
$obj = new Obj; $obj[10] = "hello"; $obj[11] = "world"; if (isset($obj[12])) { unset($obj[12]); } echo $obj[11];