在編碼中,有一個重要的事情是確保你的程式碼是可讀的、可維護的、可擴充的、易於測試的。我們可以改善這些問題的方法之一就是使用介面(interface)。
本文針對對OOP(object oriented programming)概念和PHP 中的繼承有基本理解的開發者,如果你知道如何在PHP 中使用繼承,那麼這篇文章會更容易理解一些。
基本上,介面描述了一個類別「該做什麼」。介面被用來確保實作介面的任何類別中都包含介面規定的公共方法。
介面可以:
介面不可以:
介面口用來定義一個類別中應該包含的公共方法。需要記住的是,介面只定義了方法名稱和參數以及傳回值,但不包含方法體。這是因為介面僅用於定義物件間的通信,而不是定義類別之間通信的特定行為。為了給一點上下文,這個實例展示了一個定義了幾個公共方法的範例介面:
interface DownloadableReport { public function getName(): string; public function getHeaders(): array; public function getData(): array; }
根據 php.net 介紹,介面有兩個主要用途:
介面是OOP (物件導向程式設計) 程式碼中非常重要的一部分。它們允許我們將程式碼解耦並提高可擴展性。舉一個例子,讓我們看看下面這個類別:
class BlogReport { public function getName(): string { return 'Blog report'; } }
如您所見,我們已經定義了一個帶有返回字串的方法的類別。透過這樣做,我們已經確定了方法的行為,因此我們可以看到getName()
是如何建構傳回的字串的。但是,假設我們在另一個類別中的程式碼中呼叫此方法。另一個類別並不會關心字串是如何建構的,它只關心它是否被傳回。例如,讓我們看看如何在另一個類別中呼叫此方法:
class ReportDownloadService { public function downloadPDF(BlogReport $report) { $name = $report->getName(); // 在这里下载文件... } }
儘管上面的程式碼已經可以用了,讓我們設想一下,若是我們現在想要在UserReport
類別中增加一個下載使用者報告的方法。當然,我們不能使用 ReportDownloadService
中已經存在的方法,因為我們已經強制只能傳入一個 BlogReport
類別。因此,我們必須重新命名現有方法,再新增一個新的方法。像這樣:
class ReportDownloadService { public function downloadBlogReportPDF(BlogReport $report) { $name = $report->getName(); // 在这下载文件... } public function downloadUsersReportPDF(UsersReport $report) { $name = $report->getName(); // 在这下载文件... } }
雖然你實際上看不到,但我們假設上述的類別中,其餘的方法都使用相同的程式碼來建立下載。我們可以將公共的程式碼提升為方法,但我們仍然會有一些公共的程式碼。除此之外,我們還有多個進入這個類別的幾乎是相同程式碼的入口。這可能會在未來嘗試擴展程式碼或添加測試功能時導致額外的工作量。
舉個例子,我們建立一個新的 AnalyticsReport
;我們現在需要為這個類別增加一個新的 downloadAnalyticsReportPDF()
方法。這時你可能會觀察到這個文件正在快速成長。這時就是使用介面的絕佳時機。
我們從建立一個介面開始。我們要建立一個叫做DownloadableReport
的接口,並且這樣定義:
interface DownloadableReport { public function getName(): string; public function getHeaders(): array; public function getData(): array; }
我們現在要更改BlogReport
和 UsersReport
來實現 DownloadableReport
接口,如下所示。我故意寫錯了 UsersReport
的程式碼來示範一些東西。
class BlogReport implements DownloadableReport { public function getName(): string { return '博客报告'; } public function getHeaders(): array { return ['头在这']; } public function getData(): array { return ['报告的数据在这里']; } }
class UsersReport implements DownloadableReport { public function getName() { return ['用户报告']; } public function getData(): string { return '报告的数据在这里'; } }
如果我們嘗試執行這段程式碼,我們會得到報錯,原因是:
getHeaders()
方法。getName()
方法返回类型在接口中定义的方法返回值类型中。getData()
方法定义了返回类型,但和接口中定义的返回类型不同。因此,要让 UsersReport
正确地实现 DownloadableReport
接口,我们需要作如下变动:
class UsersReport implements DownloadableReport { public function getName(): string { return '用户报告'; } public function getHeaders(): array { return []; } public function getData(): array { return ['报告的数据在这里']; } }
现在我们两个报告类都实现了相同的接口,我们可以像这样更新我们的 ReportDownloadService
:
class ReportDownloadService { public function downloadReportPDF(DownloadableReport $report) { $name = $report->getName(); // 在这下载文件 } }
现在我们向 downloadReportPDF()
方法传入了 UsersReport
或 BlogReport
对象,没有任何错误出现。这是因为我们现在知道了报告类所需要的必要方法,并且报告类会按照我们预期的类型返回数据。
向方法传入接口,而不是向类传入接口,这样的结果使得 ReportDownloadService
和报告类产生松散耦合,这根据的是方法做什么,而不是如何做。
如果我们想要创建一个新的 AnalyticsReport
,我们需要让它实现相同的接口,然后它就会允许我们将报告类实例传入相同的 downloadReportPDF()
方法中,而不用添加其他新的方法。如果你正在构建自己的应用或框架,想要让其他开发人员有创建给他们自己的类的功能,这将非常有用。举个例子,在 Laravel 中,你可以通过实现 Illuminate\Contracts\Cache\Store
接口来创建自定义的缓存类。
除了使用接口来改进代码外,我更倾向于喜欢接口的「代码即文档」特性。举个例子,如果我想要知道一个类能做什么和不能做什么,我更喜欢在查看类之前先查看接口。它会告诉我所有可调用的方法,而不需要关心这些方法具体是怎么运行的。
对于像我这样的 Laravel 开发者来说,值得注意的一件事是,你会经常看到 接口 interface
和 契约 contract
交替使用。根据 Laravel 文档,「Laravel 的契约是一组定义了框架提供的核心服务的接口」。因此,契约是一个接口,但接口不一定是契约。通常来说,契约只是框架提供的一个接口。有关契约的更多内容,我建议阅读一下 文档,因为我认为它在契约的理解和使用途径上有很好的见解。
阅读本文后,我们希望你能简要地了解到了什么是接口,如何在 PHP 中使用接口,以及使用接口的好处。
对于我的 Laravel 开发者读者们,我下周会更新一篇新的博文,讲解如何在 Laravel 中使用接口实现桥接模式。如果你感兴趣,可以订阅我的网站更新通知,这样当我更新时,你就会收到通知。
如果这篇文章有助于你理解 PHP 接口,我期待在评论区听到你的声音。继续去创造令人惊叹的作品吧!
非常感谢 Aditya Kadam 、 Jae Toole 和 Hannah Tinkler 帮助我校对和改进这篇文章。
原文地址:https://dev.to/ashallendesign/using-interfaces-to-write-better-php-code-391f
译文地址:https://learnku.com/php/t/62228
以上是一文講解介面意義及如何用介面寫高品質PHP程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!