首頁  >  文章  >  後端開發  >  什麼是介面?如何在PHP中使用介面編寫優雅的程式碼​​?

什麼是介面?如何在PHP中使用介面編寫優雅的程式碼​​?

青灯夜游
青灯夜游轉載
2022-07-25 20:17:183214瀏覽

什麼是介面?如何在PHP中使用介面?這篇文章帶大家聊聊使用介面寫更優雅的 PHP 程式碼,希望對大家有幫助!

什麼是介面?如何在PHP中使用介面編寫優雅的程式碼​​?

在程式設計中,確保程式碼可讀、可維護、可擴展和易於測試是很重要的;而使用接口,恰恰是我們改進程式碼中所有這些因素的方法之一。

目標讀者

本文的目標讀者是對 OOP(物件導向程式設計)概念有基本了解並在 PHP 中使用繼承的開發人員。如果你知道如何在 PHP 程式碼中使用繼承,那麼你應該可以很好地理解本文。

什麼是介面?

簡而言之,介面只是對類別應該做什麼的描述,它們可用於確保實現該介面的任何類別都將包括在其內部定義的每個公共方法。

介面可以

  • 用於定義類別的公共方法;
  • 用於定義類別的常數。

介面不可以

  • 被實例化;
  • 用於定義類別的私有或受保護方法;
  • 用來定義類別的屬性。

介面是用來定義一個類別應該包括的公共方法的。記住,你只需要在介面裡定義方法的簽名,而不需要包含方法的主體(就像通常在類別中看到的方法一樣)。 **這是因為介面僅用於定義物件之間的通信,而不是像在類別中那樣定義通信和行為。 **為了說明這個問題,下面展示了一個定義了幾個公共方法的範例介面:

interface DownloadableReport
{
    public function getName(): string;

    public function getHeaders(): array;

    public function getData(): array;
}

根據php.net 文件我們可以知道,介面有兩個主要用途:

  • 允許開發者建立不同類別的對象,這些物件可以互換使用,因為它們實作了相同的一個或多個介面。常見的例子包含:多個資料庫存取服務、多個支付網關、不同的快取策略等。不同的實作之間可以互換,而不需要對使用它們的程式碼進行任何修改。

  • 允許函數或方法接受符合介面的參數並對其進行操作,而不關心該物件還可以做什麼或它是如何實現的。這些介面通常被命名為 IterableCacheableRenderable 等,來說明這些介面的實際意義。

在 PHP 中使用介面

介面是 OOP(物件導向程式設計)程式碼庫的重要部分。介面能讓我們降低程式碼耦合併提高可擴展性。舉個例子,讓我們看看下面這個類別:

class BlogReport
{
    public function getName(): string
    {
        return 'Blog report';
    }
}

如你所見,我們定義了一個類,類別中有一個函數,傳回一個字串。這樣一來,我們定義了該方法的行為,所以我們知道 getName() 是如何傳回字串的。不過,假設我們在另一個類別呼叫這個方法;這個類別不需要關心這個字串如何建構的,它只關心​​該方法是否回傳內容。舉例來說,讓我們看看如何在另一個類別中呼叫此方法:

class ReportDownloadService
{
    public function downloadPDF(BlogReport $report)
    {
        $name = $report->getName();

        // 下载文件……
    }
}

儘管上面的程式碼正常運行,但我們設想一下,現在想為UsersReport 類別中增加下載用戶報告的功能。顯然,我們不能使用 ReportDownloadService 中的現有方法,因為我們已經強制規定方法只能傳遞 BlogReport 類別。因此,我們必須修改把原有的下載方法名稱改掉(避免重名),然後另外再加入一個類似的方法,如下所示:

class ReportDownloadService
{
    public function downloadBlogReportPDF(BlogReport $report)
    {
        $name = $report->getName();

        // 下载文件……
    }

    public function downloadUsersReportPDF(UsersReport $report)
    {
        $name = $report->getName();

        // 下载文件……
    }
}

假設上面的方法中的下載檔案部分(註解掉的部分)使用了相同的程式碼,而且我們可以將這些相同的程式碼單獨寫成一個方法,但我們仍會有一些重複的程式碼(譯者註:指的是每個方法中都會有 $name = $report->getName();)以及有多個幾乎相同的類別的入口。這可能會為將來擴充程式碼或測試帶來額外的工作量。

例如,假設我們建立了一個新的 AnalyticsReport;我們現在需要在該類別中新增一個新的 downloadAnalyticsReportPDF() 方法。你可以清楚的看到這個文件將如何膨脹(譯者註:指每增加一個類型,就要增加一個下載方法)。這就是一個使用介面的完美場景!

讓我們從建立第一個介面開始:讓我們將其命名為DownloadableReport,定義如下:

interface DownloadableReport
{
    public function getName(): string;

    public function getHeaders(): array;

    public function getData(): array;
}

我們現在可以更新BlogReportUsersReport 來實作DownloadableReport 接口,如下例所示。但請注意,作為演示用途,我故意把UsersReport 中的程式碼寫錯了:

class BlogReport implements DownloadableReport
{
    public function getName(): string
    {
        return 'Blog report';
    }

    public function getHeaders(): array
    {
        return ['The headers go here'];
    }

    public function getData(): array
    {
        return ['The data for the report is here.'];
    }
}
class UsersReport implements DownloadableReport
{
    public function getName()
    {
        return ['Users Report'];
    }

    public function getData(): string
    {
        return 'The data for the report is here.';
    }
}

但當我們嘗試運行程式碼的時候,我們將會收到錯誤,原因如下:

  • 缺少 getHeaders() 方法.

  • getName() 方法不包括接口的方法签名中定义的返回类型。

  • getData() 方法定义了一个返回类型,但它与接口的方法签名中定义的类型不同。

因此,为了修复 UsersReport 使其正确实现 DownloadableReport 接口,我们可以将其修改为:

class UsersReport implements DownloadableReport
{
    public function getName(): string
    {
        return 'Users Report';
    }

    public function getHeaders(): array
    {
       return [];
    }

    public function getData(): array
    {
        return ['The data for the report is here.'];
    }
}

现在两个报告类都实现了相同的接口,我们可以这样更新我们的 ReportDownloadService

class ReportDownloadService
{
    public function downloadReportPDF(DownloadableReport $report)
    {
        $name = $report->getName();

        // 下载文件……
    }

}

我们现在可以把 UsersReportBlogReport 对象传入 downloadReportPDF 方法中,而且不会出现任何错误。这是因为我们知道该对象实现了报告类的必要方法,并且将返回我们期望的数据类型。

通过向方法传递了一个接口,而不是一个具体的类,我们可以根据方法的实际作用(而不是方法的实现原理)来解耦 ReportDownloadService类和这些报告类。

如果我们想创建一个新的 AnalyticsReport,我们可以让它实现相同的接口。这样一来,我们不必添加任何新的方法,只需要将报告对象传递给同一个的 downloadReportPDF() 方法。如果你正在构建你自己的包或框架,接口可能对你特别有用。你只需要告诉使用者要实现哪个接口,然后他们就可以创建自己的类。例如,在 Laravel 中,我们可以通过实现 Illuminate\Contracts\Cache\Store 接口来创建自己的自定义缓存驱动类。

除了能改进代码之外,我喜欢使用接口的另一个原因是 —— 它们起到了“代码即文档”的作用。例如,如果我想弄清楚一个类能做什么,不能做什么,我倾向于先看接口,然后再看实现它的类。接口能够告诉我们所有可被调用的方法,而不需要我们过多地关心这些方法的底层实现方式是怎样的。

值得注意的是,Laravel 中的“契约(contract)”和“接口(interface)”这两个词语是可互换的。根据 Laravel 文档,“契约是一组由框架提供的核心服务的接口”。所以,记住:契约是一个接口,但接口不一定是契约。通常情况下,契约只是框架提供的一个接口。关于使用契约的更多信息,我建议大家可以阅读这一篇文档。它很好地剖析了契约究竟是什么,也对使用契约的方式与场景做了一定的叙述。

小结

希望通过阅读这篇文章,你能对什么是接口、如何在 PHP 中使用接口以及使用接口的好处有一个简单的了解。

原文地址:https://dev.to/ashallendesign/using-interfaces-to-write-better-php-code-391f

原文作者:Ash Allen

译者:kamly、jaredliw

推荐学习:《PHP视频教程

以上是什麼是介面?如何在PHP中使用介面編寫優雅的程式碼​​?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除