Laravel深刻學習3 - 接口約定

聲明:本文並不是博主原創,而是來自對《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。

相關文章
相關標籤/搜索