天天看點

PHP 新特性:如何善用接口與Trait

首先!

接口也可以繼承,通過使用 ​​extends​​ 操作符。

案例:

<?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)
    {
    }
}      

然後!

我們在來說說我們的主題!

接口不是新特性但是很重要,接口是兩個php對象的契約。其目的不是讓一個對象依賴另一個對象的身份,而是依賴另一個對象的能力。接口把我們的代碼和依賴解耦,而且允許我們的代碼依賴任何實作了預期接口的第三方代碼。我們不關心第三方代碼如何實作接口,隻去關心他有沒有去實作接口。

如果我們寫的類去處理特定的對象, 那麼類的功能就被限定了,隻能處理那個類。但是我們的對象如果是處理的接口,那麼代碼立即就能知道如何處理實作這一接口的任何對象,我們的代碼不管接口如何實作隻需要關心有沒有實作。

文檔處理類實作

<?php

class DocumentStore{

protected $data = [];

/**

* 參數限定為 Documentable 對象,這是一個接口

*/

public function addDocument(Documentable $document){

$key = $document->getId();

$value = $document->getContent();

$this->data[$key] = $value;

}

public function getDocuments(){

return $this->data;

}

}      

這個是我們的文檔處理類,它面向的是接口操作 Documentable 實作:

<?php

interface Documentable{

public function getId();

public function getContent();

}      

具體實作接口的類,比如是從html獲得的文檔 實作:

<?php

class HtmlDocument implements Documentable{

protected $url;

publicfunction__construct($url)

{

$this->url = $url;

}

public function getId()

{

return $this->url;

}

public function getContent()

{

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $this->url);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);

curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

curl_setopt($ch, CURLOPT_MAXREDIRS, 3);

$html = curl_exec($ch);

curl_close($ch);

return $html;

}

}      

讀取流資料的文檔 實作:

<?php

class StreamDocument implements Documentable{

protected $resource;

protected $buffer;

publicfunction__construct($resource, $buffer = 4096)

{

$this->resource = $resource;

$this->buffer = $buffer;

}

public function getId()

{

return 'resource-' . (int)$this->resource;

}

public function getContent()

{

$streamContent = '';

rewind($this->resource);

while(feof($this->resource) === false) {

$streamContent .= fread($this->resource, $this->buffer);

}

return $streamContent;

}

}      

具體使用 :

<?php

require 'Documentable.php';

require 'DocumentStore.php';

require 'HtmlDocument.php';

require 'StreamDocument.php';

require 'CommandOutputDocument.php';

$documentStore = newDocumentStore();

// Add HTML document

$htmlDoc = new HtmlDocument('http://php.net');

$documentStore->addDocument($htmlDoc);

// Add stream document

$streamDoc = new StreamDocument(fopen('stream.txt', 'rb'));

$documentStore->addDocument($streamDoc);

// Add terminal command document

$cmdDoc = new CommandOutputDocument('cat /etc/hosts');

$documentStore->addDocument($cmdDoc);

print_r($documentStore->getDocuments());      

需要說明的是參數類型 addDocument 參數類型限定為 Documentable 實作該接口的對象都可以做參數。

性狀( trait )

性狀是類的部分實作,可以混入一個或者多個現有的類實作中,有兩個作用:

1表明類可以做什麼;

2 提供子產品化實作;

使用場景:

我們做面向對象的開發的時候都會通過基類實作基本功能,完後子類具體實作詳細的功能,各類之間有明顯的自然的繼承關系,如果有一個邏輯既不屬于A類也不屬于B類,那麼在性狀出現之前我們怎麼解決:

解決辦法一:做一個父類 讓A, B都繼承,這樣做的缺點是,強制把兩個不相關的類繼承同一父類,結構混亂破壞了封裝。

解決方法二:做一個接口,讓A, B都去實作這個接口,強于上一個方法,但是缺點是相同的邏輯會在多個類中實作,代碼邏輯備援,加大維護成本。

解決辦法三:使用性狀(trait)推薦做法。

定義性狀:

<?php

// 推薦把性狀當類看待,一個檔案定義一個性狀

trait MyTrait {

protected $p1;

public $p2;

public function f1(){

}

}      

使用性狀:

<?php

class MyClass{

use MyTrait;

}