聲明:本文並不是博主原創,而是來自對《Laravel 4 From Apprentice to Artisan》閱讀的翻譯和理解,固然也不是原汁原味的翻譯,能保證90%的原汁性,另外由於是理解翻譯,確定會有錯誤的地方,歡迎指正。前端
歡迎轉載,轉載請註明出處,謝謝!程序員
Water Fowl 這裏我不知道該怎麼翻譯,按理解就將他翻譯成弱類型?web
以前的文章中,涵蓋了依賴注入的基礎知識:什麼是依賴注入;如何實現;以及他有什麼好處。在舉例的代碼中,也展現瞭如何將接口注入到類之中。在咱們繼續深刻以前,有必要深刻一下接口相關的內容,由於不少PHP開發人員對接口都有至關程度的不熟練。後端
在我成爲一個PHP程序員以前,我是寫.NET的。難道我喜歡痛苦或者其餘什麼的麼?在.NET中接口無處不在,事實上.NET框架的不少核心內容就是接口,還有一個好處:不少象.NET的語言如C#、VB等都是_強類型_的。一般,傳入方法的原生對象,必須預先定義好相關的_類型_。例以下面的C#代碼:安全
public int BillUser(User user) { this.biller.bill(user.GetId(), this.amount) }
咱們不只定義了傳入參數的_類型_,函數返回的類型都是已經定義好的。C#提倡_安全類型_。函數BillUser
方法傳入的參數必須是User
對象。框架
PHP是一種_弱類型_語言。在弱類型語言中,對象中可用的方法取決於其方法的使用形式,而非方法繼承或者實現的位置。例如:ide
public function billUser($user) { $this->biller->bill($user->getId(), $this->amount); }
咱們無需告訴方法類型的參數是什麼,咱們能夠傳入任意對象,只要他有getId
方法就行。這就是弱類型編碼的例子。若是一個東西看起來象鴨子,叫聲也象,那麼他就是一個鴨子。換言之,若是一個對象象user,行爲也象user,那麼他就是一個user對象。函數
難道,PHP就沒有強類型特徵?答案是明確的,確定有!PHP實際上是一種強類型和弱類型的結合體。爲了說明這一點,咱們修改下上例的代碼:測試
public function billUser(User $user) { $this->biller->bill($user->getId(), $amount); }
在方法參數中,加入User
約定後,就能確保傳入方法的參數必須是User
的實例化對象或者是繼承自User
的一個實例。this
兩種類型各有優劣。強類型語言中,編譯器一般提供編譯檢查錯誤的功能,它也是很是有用的。方法的輸入和輸出都是明確的。
與此同時,強類型的代碼看起來也是很生硬的。好比Eluquent ORM中提供的動態方法whereEmailOrName
就不能象C#那樣明確參數和返回值的類型。這裏不討論孰優孰劣,咱們各取所長,可是,不假思索的死認某一種方式確定會埋下不少坑。
接口就是約定,他不包含具體的代碼實現,而定義了對象須要實現的一系列的方法。若是一個對象實現了某個接口,那麼這個接口的方法確定都能在這個實例對象中使用。經過約定固化了某些方法的實現,這種_多態_就能保證語言的類型安全。
什麼是多態?
多態含義很廣,能夠理解爲一種實體的多種形式。在本書中,咱們指代接口的多種實現方式。例如:
UserRepositoryInterface
能夠有MySQL和Redis兩種存儲實現方式,可是每一種都是UserRepositoryInter
接口的實現。
爲了說明強類型在接口中的靈活和重要性,咱們來實現以下一個酒店預訂的例子:
interface ProviderInterface { public function getLowestPrice($location); public function book($location); }
當用戶預訂房間是,咱們想將此事件記錄到系統中。咱們在User
類中添加以下方法:
class User { public function bookLocation(ProviderInterface $provider, $location) { $amountCharged = $provider->book($location); $this->logBookedLocation($location, $amountCharged); } }
咱們限定了參數$provider
的類型,User
類中就能假定book
方法是可安全調用的,這就使得bookLocation
有較強的操做性,咱們不用關心酒店是如何實現房間預訂這一過程。下面的代碼就能體現這一特性:
$location = 'Hilton, Dallas'; $cheapestProvider = $this->findCheapest($location, array( new PricelineProvider, new OrbitzProvider, )); $user->bookLocation($cheapestProvider, $location);
贊!咱們不用關心那家酒店最便宜,只須要將他傳入User
實例中就能成功預訂房間。由於User
對象要求傳入的參數是繼承自ProviderInterface
的對象,將來添加更多的酒店提供商,都能使咱們的代碼穩定的運行。
忘掉細節
記住,接口_不實現_任何細節,只是簡單的定義類必須實現的方法。
當團隊構建大型應用時,不一樣的模塊進程是不一樣的。好比,有人處理數據層,有人處理前端web、控制器層。前端開發項測試本身的控制器,可是後端人員開發進度緩慢。可是,若是咱們能約定好接口,後端人員只須遵循接口定義:
interface OrderRepositoryInterface { public function getMostRecent(User $user); }
一旦約定了接口,前端開發人員,在代碼沒有實現的狀況下,也能測試本身的控制器!這樣整個應用中就不用擔憂不一樣模塊的開發進度,也不會影響到正常的測試用例的編寫。更深一點來講,這種方法不會影響到其餘組件的開發,作到了無知是福。咱們不須要讓咱們的類必須知道其餘類是_怎麼_實現的,只須要知道他_可以_幹什麼。如今,咱們已經定義了接口,那麼咱們能夠繼續咱們控制器代碼的實現了:
class OrderController { public function __construct(OrderRepositoryInterface $orders) { $this->orders = $orders; } public function getRecent() { $recent = $this->orders->getMostRecent(Auth::user()); return View::make('orders.recent', compact('recent')); } }
前端開發人員能夠本身實現一個「假」接口,來測試應用試圖中須要填充的數據。
class DummyOrderRepository implements OrderRepositoryInterface { public function getMostRecent(User $user) { return array('Order 1', 'Order 2', 'Order 3'); } }
接口實現以後,咱們就能將其綁定到容器中,就能在整個應用中使用他了:
App::bind('OrderRepositoryInterface', 'DummyOrderRepository');
當後端開發人員實現了他的模塊,好比:RedisOrderRepository
。咱們再次經過修改綁定將其應用到項目之中。
接口大綱
接口在被用來定義項目「骨架」上是很是有用的。在項目組件設計階段能夠促進團隊間設計討論。好比定義
BillingNotifierInterface
接口,並討論接口相應的方法,在敲代碼前就能用接口定義出一套好的API。