單一職責 (SRP)、開放/封閉 (OCP)、里氏替換、介面隔離和依賴倒置。每次編寫程式碼時都應指導您的五個敏捷原則。
告訴您任何一項 SOLID 原則比另一項更重要是不公平的。然而,可能沒有一個比依賴倒置原則(簡稱 DIP)對您的程式碼產生如此直接和深遠的影響了。如果您發現其他原則難以掌握或應用,請從此原則開始,然後將其餘原則應用於已遵循 DIP 的程式碼。
A.高層模組不應該依賴低層模組。兩者都應該依賴抽象。
B. 抽像不應依賴細節。細節應取決於抽象。
這個原則由Robert C. Martin 在他的《敏捷軟體開發、原則、模式和實踐》一書中定義,後來在《C# 中的敏捷原則、模式和實踐》一書中重新發布了C# 版本,這是最後一個原則五個SOLID 敏捷原則。
在我們開始編碼之前,我想先告訴你一個故事。在Syneto,我們並不總是對我們的程式碼那麼小心。幾年前,我們知之甚少,儘管我們盡力做到最好,但並非所有專案都那麼好。我們經歷了地獄又回來了,我們透過嘗試和錯誤學到了很多。
Bob 叔叔(Robert C. Martin)的 SOLID 原則和簡潔架構原則改變了我們的遊戲規則,並以難以描述的方式改變了我們的編碼方式。簡而言之,我將嘗試舉例說明 DIP 實施的一些對我們的專案產生重大影響的關鍵架構決策。
大多數 Web 專案包含三種主要技術:HTML、PHP 和 SQL。我們正在討論的這些應用程式的特定版本或您使用的 SQL 實作類型都無關緊要。問題是,來自 HTML 表單的資訊必須以某種方式最終進入資料庫。兩者之間的黏合劑可以由 PHP 提供。
最重要的是,這三種技術很好地代表了三個不同的架構層:使用者介面、業務邏輯和持久性。我們稍後將討論這些層的含義。現在,讓我們專注於一些奇怪但經常遇到的解決方案,以使這些技術協同工作。
我多次看到一些專案在HTML 檔案內的PHP 標籤中使用SQL 程式碼,或PHP 程式碼回顯HTML 頁面並直接解釋$_GET
或$_POST
全域變數。但為什麼這樣不好呢?
上面的圖像代表了我們在上一段中描述的原始版本。箭頭代表各種依賴關係,我們可以得出結論,基本上一切都依賴一切。如果我們需要更改資料庫表,我們最終可能會編輯 HTML 文件。或者,如果我們更改 HTML 中的字段,最終可能會更改 SQL 語句中的列名稱。或者,如果我們看第二個模式,如果HTML 發生變化,我們很可能需要修改PHP,或者在非常糟糕的情況下,當我們從PHP 檔案內部生成所有HTML 內容時,我們肯定需要將PHP 檔案更改為修改HTML 內容。因此,毫無疑問,類別和模組之間的依賴關係是曲折的。但事情並沒有就此結束。可以儲存程式; SQL 表中的 PHP 程式碼。
#在上面的架構中,對 SQL 資料庫的查詢傳回使用表中的資料產生的 PHP 程式碼。這些 PHP 函數或類別正在執行其他 SQL 查詢,這些查詢會傳回不同的 PHP 程式碼,並且循環繼續,直到最終獲取並傳回所有資訊...可能會返回 UI。
我知道這對你們許多人來說可能聽起來很離譜,但如果你們還沒有參與過以這種方式發明和實施的項目,那麼你們在未來的職業生涯中肯定會這樣做。大多數現有項目,無論使用何種程式語言,都是根據舊原則編寫的,由那些不關心或不知道做得更好的程式設計師編寫。如果您正在閱讀這些教程,那麼您很可能已經達到了更高的水平。您已經準備好,或準備好尊重您的職業,擁抱您的技藝,並做得更好。
另一個選擇是重複前人所犯的錯誤並承擔後果。在Syneto,當我們的一個專案由於其舊的和相互依賴的架構而達到幾乎無法維護的狀態並且我們基本上不得不永遠放棄它時,我們決定不再走那條路。從那時起,我們一直努力擁有一個乾淨的架構,正確尊重 SOLID 原則,最重要的是依賴倒置原則。
#這個架構的神奇之處在於依賴關係是如何指向的:
如果您尊重經典的敏捷設計模式,那麼在架構層級應用依賴倒置原則 (DIP) 是非常容易的。在業務邏輯中練習和舉例也很容易,甚至很有趣。我們將想像一個電子書閱讀器應用程式。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new PDFReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } class PDFReader { private $book; function __construct(PDFBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook { function read() { return "reading a pdf book."; } }
我們開始將電子閱讀器開發為 PDF 閱讀器。到目前為止,一切都很好。我們有一個使用 PDFBook
的 PDFReader
類別。讀者上的 read()
函數委託給本書的 read()
方法。我們只是透過在 PDFBook
的 reader()
方法傳回的字串的關鍵部分之後進行正規表示式檢查來驗證這一點。
請記住,這只是一個範例。我們不會實作PDF文件或其他文件格式的閱讀邏輯。這就是為什麼我們的測試將只檢查一些基本字串。如果我們要編寫真正的應用程序,唯一的區別是我們如何測試不同的文件格式。依賴結構與我們的範例非常相似。
拥有一个使用 PDF 书籍的 PDF 阅读器对于有限的应用程序来说可能是一个合理的解决方案。如果我们的范围是编写一个 PDF 阅读器,仅此而已,那么它实际上是一个可以接受的解决方案。但我们想编写一个通用的电子书阅读器,支持多种格式,其中我们第一个实现的版本是 PDF。让我们重命名我们的读者类。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } class EBookReader { private $book; function __construct(PDFBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook { function read() { return "reading a pdf book."; } }
重命名没有功能上的反作用。测试仍在通过。
测试于下午 1:04 开始...
PHPUnit 3.7.28 由 Sebastian Bergmann 编写。
时间:13 毫秒,内存:2.50Mb
好的(1 个测试,1 个断言)
进程已完成,退出代码为 0
但它具有严重的设计效果。
我们的读者变得更加抽象。更一般。我们有一个通用的 EBookReader
,它使用非常具体的书籍类型 PDFBook
。抽象取决于细节。我们的书是 PDF 类型这一事实应该只是一个细节,任何人都不应该依赖它。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } } interface EBook { function read(); } class EBookReader { private $book; function __construct(EBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook implements EBook{ function read() { return "reading a pdf book."; } }
反转依赖关系最常见、最常用的解决方案是在我们的设计中引入一个更抽象的模块。 “OOP 中最抽象的元素是接口。因此,任何其他类都可以依赖于接口并且仍然遵循 DIP”。
我们为读者创建了一个界面。该接口名为 EBook
,代表 EBookReader
的需求。这是尊重接口隔离原则 (ISP) 的直接结果,该原则提倡接口应反映客户端需求的理念。接口属于客户端,因此它们的命名反映了客户端需要的类型和对象,并且它们将包含客户端想要使用的方法。 EBookReader
使用 EBooks
并拥有 read()
方法是很自然的。
我们现在有两个依赖项,而不是单个依赖项。
EBookReader
指向 EBook
接口,并且它是类型用法。 EBookReader
使用 EBooks
。PDFBook
指向相同的 EBook
接口,但它是类型实现。 PDFBook
只是 EBook
的一种特殊形式,因此实现了该接口来满足客户的需求。不出所料,该解决方案还允许我们将不同类型的电子书插入阅读器。所有这些书籍的唯一条件是满足 EBook
接口并实现它。
class Test extends PHPUnit_Framework_TestCase { function testItCanReadAPDFBook() { $b = new PDFBook(); $r = new EBookReader($b); $this->assertRegExp('/pdf book/', $r->read()); } function testItCanReadAMobiBook() { $b = new MobiBook(); $r = new EBookReader($b); $this->assertRegExp('/mobi book/', $r->read()); } } interface EBook { function read(); } class EBookReader { private $book; function __construct(EBook $book) { $this->book = $book; } function read() { return $this->book->read(); } } class PDFBook implements EBook { function read() { return "reading a pdf book."; } } class MobiBook implements EBook { function read() { return "reading a mobi book."; } }
这又将我们引向开闭原则,并且圆圈是闭合的。
依赖倒置原则是引导或帮助我们尊重所有其他原则的原则。尊重 DIP 将:
就是这样。我们完了。所有有关 SOLID 原理的教程均已完成。对我个人来说,发现这些原则并在实施项目时牢记它们是一个巨大的变化。我完全改变了我对设计和架构的思考方式,我可以说从那时起我从事的所有项目都变得更加容易管理和理解。
我认为 SOLID 原则是面向对象设计最基本的概念之一。这些概念必须指导我们使我们的代码变得更好,并使我们作为程序员的生活变得更加轻松。设计良好的代码更容易让程序员理解。计算机很聪明,无论代码多么复杂,它们都可以理解。另一方面,人类能够在活跃、专注的头脑中保存的事物数量有限。更具体地说,此类事物的数量是神奇数字七、正负二。
我們應該努力讓我們的程式碼圍繞這些數字構建,有幾種技術可以幫助我們做到這一點。函數長度最多為四行(五行包括定義行),以便它們可以同時適合我們的頭腦。壓痕深度未超過五層。方法不超過九個的類別。通常使用五到九個類的設計模式。我們在上面的模式中的高層設計使用了四到五個概念。有五個 SOLID 原則,每個原則需要五到九個子概念/模組/類別來舉例說明。程式設計團隊的理想規模是五到九人。公司中理想的團隊數量是五到九個。
正如您所看到的,神奇的數字七、正負二就在我們身邊,那麼為什麼您的程式碼應該有所不同呢?
以上是SOLID: 第四部分 - 依賴倒置原則的詳細內容。更多資訊請關注PHP中文網其他相關文章!