PHP的介面自始至終一直在被爭議,有人說介面很好,有人說介面像雞肋。首先要明白,好喝不好的判斷標準是什麼。無疑,這是和Java/C++相比。在上面的例子中,以及討論了PHP的介面在“面向契約編程”中是不足的,並沒有起到應有的作用。
其實,machine類的聲明應該在plain類前面。介面提供了一套規範,這是系統提供的,然後machine類提供一組針對介面的API並實現,最後才是自訂的類。在Java裡,介面之所以盛行(多線程的runable介面,容器的collection介面等)就是因為系統為我們做了前面兩部分的工作,而程式員,只需要去寫具體的實作類別,就能保證介面可用可控。
為什麼要用介面?介面到底有什麼好處?介面本身並不提供實現,只是提供一個規範。如果我們知道一個類實現了某個介面,那麼就知道了可以調用該介面的那些方法,我們只需要知道這些就夠了。
PHP中,介面的語義是有限的,使用介面的地方並不多,PHP中介面可以淡化為設計文檔,起到一個團隊基本契約的作用,代碼如下所示:
<?phpinterface Cache{/** * describe:緩衝管理,專案經理定義介面,技術人員負責實現 */ const maxKey = 10000; //最大換存量 public function getCache($key); //擷取緩衝 public function setCache($key,$value); //設定緩衝 public function flush(); //清空緩衝}
由於PHP是弱類型,且強調靈活,所並不推薦大規模使用介面,而是僅在部分“核心”代碼中使用介面,因為PHP中的介面已經失去很多介面應該具有的語義。從語義上考慮,可以更多地使用抽象類別。至於抽象類別和介面的比較,不再贅述。
另外,PHP5對物件導向的特種做了許多增強,其中就有一個SPL(標註PHP庫)的嘗試,SPL中實現一些介面,其中最主要的就是iterator迭代器介面,通過實現這個介面,就能使對象能用於foreach結構,從而在使用形式上比較統一。比如SPL中一個DirectoryIterator類,這個類在整合SplFileInfo類的同時,實現Iterator、Traversable、SeekableIterator這三個介面,那麼這個類的執行個體可以獲得父類SplFileInfo的全部功能外,還能夠實現Iterator介面所展示的那些操作。
Iterator介面的原型如下:
* current() This methodreturns the current index's value. You are solely responsible for tracking what thecurrent index is as the interface does not do this for you.*key() This method returns the value of the current index's key. For foreach loops this is extremely important so that the key value can be populated. *next() This method moves the internal index forward one entry. *rewind() This method should reset the internal index to the first element.*valid() This method should return true or false if there is a current element. It is called after rewind() or next().
如果一個類聲明了實現Iterator,就必須實現這五個方法,如果實現了這五個方法,那麼就可以很容易對這個類的執行個體進行迭代。這裡,DirectoryIterator類之所以拿來就能用,是因為系統已經實現了Iterator介面,所以可以像下面這樣使用:
<?php$dir = new DirectoryIterator(dirname(FILE));foreach ($dir as $fileInfo){ if(!$fileInfo->isDir()) { echo $fileInfo->getFilename(),"\t",$fileInfo->getSize(),PHP_EOL; }}
可以想象,如果不用DirectoryIterator類,而是自己實現,不但代碼量增加了,而且迴圈時候的風格也不統一了。如果自己寫的類也實現了Iterator介面,那麼就可以像Iterator那樣工作。
為什麼一個類只要實現了Iterator迭代器,其對象就可以被用做foreach的對象呢?其實原因很簡單,在對PHP執行個體對象使用foreach文法時,會檢查這個執行個體有沒有實現Iterator介面,如果實現了,就會通過內建方法或使用實作類別中的方法類比foreach語句,這是不是和前面提到的toString 方法的實現很像呢?事實上,toString方法就是介面的一種變相實現。介面就是這樣,介面本身什麼也不做,系統悄悄地在內部實現了介面的行為,所以只要實現這個介面,就可以使用介面提供的方法。這就是介面“隨插即用”思想
我們都知道,介面是多多重整合的一種變相實現,而在講繼承時,我們提到了用來實現混入(Minxin)式的Traits,實際上,traits可以被視為一種加強版的介面。
來看下面的代碼:
<?phptrait Hello{ public function sayHello() { echo 'Hello '; }}trait World{ public function sayWorld() { echo 'Word'; }}class MyHelloWorld{ use Hello, World; public function sayExclamationMark() { echo '!'; }}$o = new MyHelloWorld();$o->sayHello();$o->sayWorld();$o->sayExclamationMark();
上面代碼運行結果如下:
Hello Word!
這裡的MyHelloWorld同時實現了兩個traits,從而使其可以分別調用兩個Traits裡的程式碼片段。從代碼中就可以看出,Traits和介面很像,不同的是Traits是可以匯入包含代碼的介面。從某種意義上來說,Traits和介面都是對“多重整合”的一種變相實現。
總結關於介面的幾個概念:
介面作為一種規範和契約存在。作為規範,介面應該保證可用性;作為契約介面應該保證可控性、
介面只是一個聲明,一旦使用interface關鍵字,就應該實現它。可以由程式員實現(外部介面),也可以由系統實現(內部介面)。介面本身什麼都不做,但是他可以高數我們它能做什麼。
PHP中的介面存在兩個不足,一時沒有契約限制,二是缺少足夠多的內部介面。
介面其實很簡單,但是介面的各種應用很靈活,設計模式中也有很大一部分是圍繞介面展開的。