Laravel核心內(nèi)容:契約,你了解多少?
接口如同契約。接口并不包含任何代碼實(shí)現(xiàn),只是定義了一個(gè)實(shí)現(xiàn)該接口的對(duì)象必須實(shí)現(xiàn)的一系列方法。
如果一個(gè)對(duì)象實(shí)現(xiàn)了一個(gè)接口,那么我們就能保證這個(gè)接口所定義的一系列方法都能在這個(gè)對(duì)象上調(diào)用。
由于有接口契約保證特定方法的實(shí)現(xiàn),通過多態(tài)也能使類型安全的語言變得更靈活。
關(guān)于多態(tài):多態(tài)含義很廣,從本質(zhì)上說,是一個(gè)實(shí)體擁有多種形式。在本書中,我們講多態(tài)說的是一個(gè)接口有多鐘實(shí)現(xiàn)方式。例如,UserRepositoryInterface 可以有 MySQL 和 Redis 兩種實(shí)現(xiàn),并且每一種實(shí)現(xiàn)都是 UserRepositoryInterface 的一個(gè)實(shí)例。
為了說明接口在強(qiáng)類型語言中的靈活性,我們們來寫一個(gè)簡單的酒店客房預(yù)訂代碼??紤]以下接口:(代碼可滑動(dòng)查看)
interface ProviderInterface
{
public function getLowestPrice($location);
public function book($location);
}
當(dāng)用戶預(yù)訂房間時(shí),我們需要將此事記錄在系統(tǒng)里。所以在 User 類里添加如下方法:(代碼可滑動(dòng)查看)
class User
{
public function bookLocation(ProviderInterface $provider, $location)
{
$amountCharged = $provider->book($location);
$this->logBookedLocation($location, $amountCharged);
}
}
由于我們對(duì) $provider 做了類型約束,在 User 類的 bookLocation 方法中,就可以放心大膽的認(rèn)為 $provider 實(shí)例上的 book 方法是可以調(diào)用的。
這給我們復(fù)用 bookLocation 方法帶來了靈活性,完全不必關(guān)心用戶傾向哪家酒店提供商。最后,我們編寫一些代碼來體驗(yàn)下這種靈活性:(代碼可滑動(dòng)查看)
$location = '希爾頓, 達(dá)拉斯';
$cheapestProvider = $this->findCheapest($location, array(
new PricelineProvider,
new OrbitzProvider,
));
$user->bookLocation($cheapestProvider, $location);
不管哪家酒店是最便宜的,我們都能夠?qū)⑺鼈魅?nbsp;User 對(duì)象來預(yù)訂房間了。由于 User 對(duì)象只需要有一個(gè)遵從 ProviderInterface 契約的對(duì)象實(shí)例就可以了,所以未來如果有新的酒店供應(yīng)商,我們的代碼也可以很好的工作。
忘掉細(xì)節(jié):記住,接口實(shí)際上并不做任何事情。它只是簡單的定義了實(shí)現(xiàn)類必須擁有的一系列方法。
構(gòu)建大型應(yīng)用
當(dāng)你的團(tuán)隊(duì)在構(gòu)建大型應(yīng)用時(shí),不同的功能模塊往往有著不同的開發(fā)進(jìn)度。例如,一個(gè)開發(fā)人員在開發(fā)數(shù)據(jù)層,另一個(gè)開發(fā)人員在做前端和控制器層。前端開發(fā)者想要測試他的控制器,但是后端開發(fā)進(jìn)度比較慢,無法聯(lián)調(diào)。
如果這兩個(gè)開發(fā)者能以接口或契約的方式達(dá)成協(xié)議,然后后端開發(fā)的所有類都遵循這種協(xié)議,就像下面這段代碼:(代碼可滑動(dòng)查看)
interface OrderRepositoryInterface
{
public function getMostRecent(User $user);
}
一旦建立了契約,就算契約還沒有真正實(shí)現(xiàn),前端開發(fā)者也可以測試他的控制器了!這樣一來,應(yīng)用中的不同組件就可以按不同的速度開發(fā),同時(shí)仍然允許編寫適當(dāng)?shù)膯卧獪y試。
此外,這種方式還可以使組件內(nèi)部的改動(dòng)不會(huì)影響到其它不相關(guān)的組件。要始終牢記「無知是?!埂N覀儾幌胱岊愔酪蕾囀侨绾喂ぷ鞯?,只需要知道它們能做什么。所以,先定義好契約,再來寫控制器:(代碼可滑動(dòng)查看)
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'));
}
}
前端開發(fā)者甚至可以為這接口寫個(gè)「假」實(shí)現(xiàn),然后這個(gè)應(yīng)用的視圖就可以用假數(shù)據(jù)渲染了:(代碼可滑動(dòng)查看)
class DummyOrderRepository implements OrderRepositoryInterface
{
public function getMostRecent(User $user)
{
return array('Order 1', 'Order 2', 'Order 3');
}
}
編寫好假實(shí)現(xiàn)之后,就可以在服務(wù)容器里將其綁定到契約上,然后在整個(gè)應(yīng)用中都可以調(diào)用它了:(代碼可滑動(dòng)查看)
$this->app->bind(OrderRepositoryInterface::class, function ($app) {
return new DummyOrderRepository();
});
接下來,如果后臺(tái)開發(fā)者寫完了真正的實(shí)現(xiàn)代碼,如RedisOrderRepository。服務(wù)容器中的綁定可以輕松切換到新的實(shí)現(xiàn),整個(gè)應(yīng)用將會(huì)使用開始從 Redis 讀取出來的訂單數(shù)據(jù)。
接口即綱領(lǐng):接口有助于開發(fā)應(yīng)用所提供的、已定義好的功能「框架」。 在組件的設(shè)計(jì)階段,團(tuán)隊(duì)里使用接口進(jìn)行討論是很方便的,例如,定義一個(gè) BillingNotifierInterface 接口,然后討論它提供哪些方法。在編寫任何實(shí)現(xiàn)代碼前,最好先通過接口討論達(dá)成一致,這是構(gòu)建一套好 API 的必要前提!
有一款不錯(cuò)的開源電商系統(tǒng)likeshop,給大家推薦一下,可以下載免費(fèi)商用或者學(xué)習(xí)借鑒思路,地址: https://gitee-github.com/VyN8svYL
