Interface As Contract 接口約定

Interface As Contract 接口約定

Strong Typing & Water Fowl 強類型和小鴨子

In the previous chapters, we covered the basics of dependency injection: what it is; how it is accomplished; and several of benefits. The examples in the previous chapters also demonstrated the injection of interface into our classes, so before proceeding further, it will be beneficial to talk about interfaces in depth, as many PHP developers are not familiay with them.

在之前的章節裏,涵蓋了依賴注入的基礎知識:什麼是依賴注入;如何實現依賴注入;依賴注入有什麼好處。 之前章節裏面的例子也模擬了將interface注入到classes裏面的過程。在我們繼續學習之前,有必要深入講解一下接口,而這正是很多PHP開發者所不熟悉的。

Before I was a PHP programmer, I was a .NET programmer. Do I love pain or what? In .NET, interfaces are everywhere. In fact, many interfaces are defined as part of the core .NET framework itself, and for good reason: many of the .NET languages, such as C# and VB.NET, are strongly typed. In general, you must define the type of object or primitive you will be passing into a method. For example, consider the following C# method:

在我成爲PHP程序員之前,我是寫.NET的。 你覺得我是M麼?在.NET裏可到處都是接口。 事實上很多接口是定義在.NET框架核心中了,一個好的理由是:很多.NET語言比如C#和VB.NET都是強類型的。 也就是說,你在給一個函數傳值,要麼傳原生類型對象,要麼就必須給這個對象一個明確的類型定義。比如考慮以下C#方法:


<!-- lang: c# -->
public int BillUser(User user)
{
    this.biller.bill(user.GetId(), this.amount)
}

Note that we were forced to define not only what type of arguments we will be passing to the method, but also what the method itself will be returning. C# is encouraging type safety. We will not be allowed to pass anything other than an User object into out BillUser method

注意在這裏, 我們不僅要定義傳進去的參數是什麼類型的,還要定義這個方法返回值是什麼類型的。 C#鼓勵類型安全。除了指定的User對象,它不允許我們傳遞其他類型的對象到BillUser方法中。

However, PHP is generally a duck typed language. In a duck typed language, an object’s avaliable methods determine the way it may be used, rather than its inheritance from a class or implementation of an interface. Let’s look at an example:

然而PHP是一種鴨子類型的語言。 所謂鴨子類型的語言, 一個對象可用的方法取決於使用方式, 而非這個方法從哪兒繼承或實現。來看個例子:

<!-- lang:php -->
public function billUser($user)
{
    $this->biller->bill($user->getId(), $this->amount);
}

In PHP, we did not have to tell the method what type of argument to expect. In fact, we can pass any type, so long as the object responds to the getId method. This is an example of duck typing. If the object looks like a duck, quacks like a duck, it must be a duck. Or, in this case, if the object looks like a user, and acts like a user, it must be one.

在PHP裏面,我們不必告訴一個方法需要什麼類型的參數。 實際上我們傳遞任何類型的對象都可以,只要這個對象能響應getId的調用。這裏有個關於鴨子類型(下文譯作:弱類型)的解釋:如果一個東西看起來像個鴨子,叫聲也像鴨子叫,那他就是個鴨子。 換言之在程序裏,一個對象看上去是個User,方法響應也像個User,那他就是個User。

But, does PHP have any strongly typed style features? Of course! PHP has a mixture of both strong and duck typed constructs. To illustrate this, let’s re-write our billUser method:

不過PHP到底有沒有任何強類型功能呢?當然有!PHP混合了強類型和弱類型的結構。爲了說明這點,咱們來重寫一下billUser方法:

<!-- lang:php -->
public function billUser(User $user)
{
    $this->biller->bill($user->getId(), $amount);
}

After adding the User type-hint to our method signature, we can now gurantee that all objects passed to billUsereither are a User instance, or extend the User class.

給方法加上了加上了User類型提示後, 我們可以確信的說所有傳入billUser方法的參數,都是User類或是繼承自User類的一個實例。

There are advangates and disadvantages to both typing systems. In strongly typed languages, the compiler can often provide you with through compile-time error checking, which is extremely helpful. Method inputs and outputs are also much more explicit in a strongly typed language.

強類型和弱類型各有優劣。 在強類型語言中, 編譯器通常能提供編譯時錯誤檢查的功能,這功能可是非常有用的。方法的輸入和輸出也更加明確。

As the same time, the explicit nature of strong typing can make it rigid. For example, dynamic methods such as whereEmailOrName that the Eloquent ORM offers would be impossible to implement in a strongly typed language like C#. Try to avoid heated discussions on which paradigm is better, and remember the advantages and disadvantages of each. It is not “wrong” to use the strong typing avaliable in PHP, nor is it wrong to use duck typing. What is wrong is exclusively using one or the other without considering the particular problem you are trying to solve.

與此同時,強類型的特性也使得程序僵化。比如Eloquent ORM中,類似whereEmailOrName的動態方法就不可能在C#這樣的強類型語言裏實現。我們不討論強類型弱類型哪種更好,而是要記住他們分別的優劣之處。在PHP裏面使用強類型標記不是錯誤,使用弱類型特性也不是錯誤。但是不加思索,不管實際情況去使用一種模式,這麼固執的使用就是錯的。
A Contract Example 約定的範例

Interfaces are contracts. Interfaces do not contain any code, but simply define a set of methods that an object must implement. If an object implements an interface, we are guranteed that every method defined by the interface is valid and callable on that object. Since the contract gurantees the implementation of certain methods, type safety becomes more flexible via polymorphism.

接口就是約定。接口不包含任何代碼實現,只是定義了一個對象應該實現的一系列方法。如果一個對象實現了一個接口,那麼我們就能確信這個接口所定義的一系列方法都能在這個對象上使用。因爲有約定保證了特定方法的實現標準,通過多態也能使類型安全的語言變得更靈活。

Polywhat? 多什麼肽?

Polymorphism is a big word that essentially means an entity can have multiple forms. In the context of this book, we mean that an interface can have multiple implementations. For example, a UserRepositoryInterface could have a MySQL and a Redis implementation, and both implementations would qualify as a UserRepositoryInterfaceinstance.

多態含義很廣,其本質上是說一個實體擁有多種形式。在本書中,我們講多態是一個接口有着多種實現。比如UserRepositoryInterface可以有MySQL和Redis兩種實現,每一種實現都是UserRepositoryInterface的一個實例。

To illustrate the flexibility that interfaces introduce into strongly typed languages, let’s write some simple code that books hotel rooms. Consider the following interface:

爲了說明在強類型語言中接口的靈活性,咱們來寫一個酒店客房預訂的代碼。考慮以下接口:

<!-- lang:php -->
interface ProviderInterface{
    public function getLowestPrice($location);
    public function book($location);
}

When our user books a room, we’ll want to log that in our system, so let’s add a few methods to our User class:

當用戶訂房間時,我們需要將此事記錄在系統裏。所以在User類裏面寫點方法:

<!-- lang:php -->
class User{
    public function bookLocation(ProviderInterface $provider, $location)
    {
        $amountCharged = $provider->book($location);
        $this->logBookedLocation($location, $amountCharged);
    }

Since we are type hinting the ProviderInterface, our User can safely assume that the book method will be available. This gives us the flexibility to re-use our bookLocation method regardless of the hotel provider the user prefers. Finally, let’s write some code that harnesses this flexibility:

因爲我們寫出了ProviderInterface的類型提示,該User類的就可以放心大膽的認爲book方法是可以調用的。這使得bookLocation方法有了重用性。當用戶想要換一家酒店提供商時也就更靈活。最後咱們來寫點代碼來強化他的靈活性。

<!-- lang:php -->
$location = 'Hilton, Dallas';

$cheapestProvider = $this->findCheapest($location, array(
    new PricelineProvider,
    new OrbitzProvider,
));

$user->bookLocation($cheapestProvider, $location);

Wonderful! No matter what provider is the cheapest, we are able to simply pass it along to our User instance for booking. Since our User is simply asking for an object instances that abides by the ProviderInterface contract, our code will continue to work even if we add new provider implementations.

太棒了!不管哪家是最便宜的,我們都能夠將他傳入User對象來預訂房間了。由於User對象只需要要有一個符合ProviderInterface約定的實例就可以預訂房間,所以未來有更多的酒店供應商我們的代碼也可以很好的工作。

Forget The Details 忘掉細節

Remember, interfaces don’t actually do anything, They simply define a set of methods that an implementing class must have.

記住,接口實際上不真正做任何事情。它只是簡單的定義了類們必須實現的一系列方法。

Interfaces & Team Development 接口與團隊開發

When you team is building a large application, pieces of the application progress at different speeds. For example, imagine one developer is working on the data layer, while another developer is working on the front-end and web/controller layer. The front-end developer wants to test his controllers, but the back-end is progressing slowly. However, what if the two developers can agree on an interface, or contract, that the back-end classes will abide by, such as following:

當你的團隊在開發大型應用時,不同的部分有着不同的開發速度。比如一個開發人員在製作數據層,另一個開發人員在做前端和網站控制器層。前端開發者想測試他的控制器,不過後端開發較慢沒法同步測試。那如果兩個開發者能以接口的方式達成協議,後臺開發的各種類都遵循這種協議,就像這樣:

<!-- lang:php -->
interface OrderRepositoryInterface {
    public function getMostRecent(User $user);
}

Once the contract has been agreed upon, the front-end developer can test his controller, even if no real implementation of the contract has been written! This allows components of the application to be built at different speeds, while still allowing for proper unit tests to be written. Further, this approach allows entire implementations to change without breaking other, unrelated components. Remember, ignorance is bliss. We don’t want our classes to know how a dependency does something, but only that it can. So, now that we have a contract defined, let’s write our controller:

一旦建立了約定,就算約定還沒實現,前端開發者也可以測試他的控制器了!這樣應用中的不同組件就可以按不同的速度開發,並且單元測試也可以做。而且這種處理方法還可以使組件內部的改動不會影響到其他不相關組件。要記着無知是福。我們寫的那些類們不用知道別的類如何實現的,只要知道它們能實現什麼。這下咱們有了定義好的約定,再來寫控制器:

<!-- lang:php -->
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'));
    }
}

The front-end developer could even write a “dummy” implementation of the interface, so the application’s views can be populated with some fake data:

前端開發者甚至可以爲這接口寫個“假”實現,然後這個應用的視圖就可以用假數據填充了:

<!-- lang:php -->
class DummyOrderRepository implements OrderRepositoryInterface {
    public function getMostRecent(User $user)
    {
        return array('Order 1', 'Order 2', 'Order 3');
    }
}

Once the dummy implementation has been written, it can be bound in the IoC container so it is used throughout the application:

一旦假實現寫好了,就可以被綁定到IoC容器裏,然後整個程序都可以調用他了:

<!-- lang:php -->
App::bind('OrderRepositoryInterface', 'DummyOrderRepository');

Then, once a “real” implementaion, such as RedisOrderRepository, has been written by the back-end developer, the IoC binding can simply be switched to the new implementation, and the entire application will begin using orders that are stored in Redis.

接下來一旦後臺開發者寫完了真正的實現代碼,比如叫RedisOrderRepository。那麼IoC容器就可以輕易的切換到真正的實現上。整個應用就會使用從Redis讀出來的數據。

Interface As Schematic 接口就是大綱

Interfaces are helpful for developing a “skeleton” of defined functionality provided by your application. Use them during the design phase of a component to facilicate design discussion amongst your team. For example, define a BillingNotifierInterface and discuss its methods with your team. Use the interface to agree on a good API before you actually write any implementation code!

接口在開發程序的“骨架”時非常有用。 在設計組件時,使用接口進行設計和討論都是對你的團隊有益處的。比如定義一個BillingNotifierInterface然後討論他有什麼方法。在寫任何實現代碼前先用接口討論好一套好的API!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章