核心要點
軟件編程是藝術(有時是即興創作的委婉說法)和許多行之有效的啟發式方法的平衡組合,用於解決某些問題並以體面的方式解決它們。幾乎沒有人會不同意,藝術方面是迄今為止最難磨練和提煉的方面。另一方面,駕馭啟發式方法背後的力量對於能夠開發基於良好設計的軟件至關重要。由於有如此多的啟發式方法說明軟件系統應該如何以及為什麼應該堅持特定方法,因此在 PHP 世界中沒有看到更廣泛地實施它們,這令人相當失望。例如,迪米特法則可能是該語言領域中最被低估的法則之一。實際上,該法則的“只與你的密友交談”的格言在 PHP 中似乎還處於相當不成熟的狀態,這導致了幾個面向對象代碼庫整體質量的下降。一些流行的框架正在積極推動它向前發展,試圖更加遵守該法則的戒律。為違反迪米特法則而互相指責是沒有意義的,因為減輕此類破壞的最佳方法是簡單地採取務實態度,並了解該法則下的實際內容,從而在編寫面向對象代碼時有意識地應用它。為了加入正義事業,並從實踐的角度更深入地研究該法則,在接下來的幾行中,我將通過一些實踐示例來演示,為什麼像遵守該法則的原則這樣簡單的事情在設計松耦合軟件模塊時可以真正提升效率。
了解過多並非好事
通常被稱為最少知識原則,迪米特法則所推崇的規則很容易理解。簡單地說,假設您有一個精心設計的類,它實現了一種給定的方法,那麼該方法應該被限制為調用屬於以下對象的其他方法:
儘管該列表遠非正式(對於更正式的列表,請查看維基百科),但這些要點很容易理解。在傳統設計中,一個對像對另一個對象了解太多(這隱含地包括知道如何訪問第三個對象)被認為是錯誤的,因為在某些情況下,對象必須不必要地從上到下遍歷笨拙的中間體才能找到其需要按預期工作所需的實際依賴項。出於顯而易見的原因,這是一個嚴重的設計缺陷。調用者對中間體的內部結構有相當廣泛和詳細的了解,即使這是通過幾個 getter 訪問的。此外,使用中間對象來獲取調用者所需的對象本身就說明了一個問題。畢竟,如果可以通過直接注入依賴項來實現相同的結果,為什麼還要使用如此復雜的路徑來獲取依賴項或調用其方法之一呢?這個過程根本沒有任何意義。
讓我們假設我們需要構建一個文件存儲模塊,該模塊在內部使用多態編碼器將數據拉入並保存到給定的目標文件中。如果我們故意馬虎地將模塊連接到可注入的服務定位器,則其實現將如下所示:
<?php namespace LibraryFile; use LibraryDependencyInjectionServiceLocatorInterface; class FileStorage { const DEFAULT_STORAGE_FILE = "data.dat"; private $locator; private $file; public function __construct(ServiceLocatorInterface $locator, $file = self::DEFAULT_STORAGE_FILE) { $this->locator = $locator; $this->setFile($file); } public function setFile($file) { if (!is_readable($file) || !is_writable($file)) { throw new InvalidArgumentException( "The target file is invalid."); } $this->file = $file; return $this; } public function write($data) { try { return file_put_contents($this->file, $this->locator->get("encoder")->encode($data), LOCK_EX); } catch (Exception $e) { throw new $e( "Error writing data to the target file: " . $e->getMessage()); } } public function read() { try { return $this->locator->get("encoder")->decode( @file_get_contents($this->file)); } catch(Exception $e) { throw new $e( "Error reading data from the target file: " . $e->getMessage()); } } }
省略一些不相關的實現細節,重點是 FileStorage 類的構造函數及其 write() 和 read() 方法。該類註入一個尚未定義的服務定位器的實例,稍後用於獲取依賴項(前面提到的編碼器),以便在目標文件中獲取和存儲數據。考慮到該類首先遍歷定位器,然後到達編碼器,這通常是違反迪米特法則的行為。調用者 FileStorage 對定位器的內部結構了解太多,包括如何訪問編碼器,這絕對不是我會讚揚的能力。它是一種內在地根植於服務定位器(這就是為什麼有些人將其視為反模式)或任何其他類型的靜態或動態註冊表的本質的工件,這是我之前指出的。為了更全面地了解這個問題,讓我們檢查一下定位器的實現:
(此處省略了locator和encoder的代碼,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
有了編碼器,現在讓我們一起使用所有示例類來啟動:
(此處省略了使用示例代碼,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
該法則的違反在這種情況下是一個相當隱蔽的問題,很難從表面上追踪到,除了使用定位器的mutator,這表明在某些時候,編碼器將以某種形式被FileStorage的實例訪問和使用。無論如何,我們知道違規行為就在那裡隱藏在外部世界之外,這一事實不僅揭示了太多關於定位器結構的信息,而且還將 FileStorage 類不必要地耦合到定位器本身。只需遵守該法則的規則並擺脫定位器,我們就可以消除耦合,同時為 FileStorage 提供其開展業務所需的實際協作者。途中不再有笨拙、暴露的中間體!幸運的是,所有這些廢話都可以通過一點點努力輕鬆地轉換為可工作的代碼。只需在此處查看增強的、符合迪米特法則的 FileStorage 類版本:
(此處省略了重構後的FileStorage代碼,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
這確實很容易重構。現在,該類直接使用 EncoderInterface 接口的任何實現者,避免遍歷不必要的中間體的內部結構。該示例無疑是微不足道的,但它確實說明了一個有效點,並演示了為什麼遵守迪米特法則的戒律是您可以做的最好的事情之一,以改進類的設計。但是,羅伯特·馬丁的著作《代碼整潔之道:敏捷軟件開發手冊》中深入探討了該法則的一個特例,值得特別分析。請花一點時間仔細考慮一下:如果 FileStorage 被定義為通過數據傳輸對象 (DTO) 獲取其協作者,會發生什麼情況?
(此處省略了使用DTO的代碼示例,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
這絕對是一種有趣的實現文件存儲類的方法,因為它現在使用可注入的 DTO 來在內部傳輸和使用編碼器。需要回答的問題是這種方法是否真的違反了該法則。從純粹主義的角度來看,它確實違反了,因為 DTO 無疑是一個向調用者公開其整個結構的中間體。但是,DTO 只是一種普通的數據結構,與早期的服務定位器不同,它根本沒有任何行為。而數據結構的目的正是……是的,公開其數據。這意味著,只要中間體不實現行為(這與常規類的行為完全相反,因為它公開行為而隱藏其數據),迪米特法則就會保持完整。以下代碼片段顯示瞭如何使用有問題的 DTO 來使用 FileStorage:
(此處省略了使用DTO的代碼示例,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
這種方法比直接將編碼器傳遞到文件存儲類中要麻煩得多,但該示例表明,一些乍一看似乎是公然違反該法則的棘手實現,通常是相當無害的,只要它們使用沒有任何附加行為的數據結構即可。
結束語
由於各種複雜、有時是深奧的啟發式方法在 OOP 中流行,因此添加另一個顯然對層組件的設計沒有任何明顯積極影響的原則似乎毫無意義。然而,迪米特法則絕不是一個在現實世界中幾乎沒有應用的原則。儘管名稱華麗,但迪米特法則是一個強大的範例,其主要目標是通過消除任何不必要的中間體來促進高度解耦的應用程序組件的實現。只需遵循其戒律,當然不要盲目教條主義,您就會看到代碼質量的提高。保證。
(此處省略了FAQs部分,因為與上一個輸出一致,為了避免重複,此處不再贅述。)
以上是PHP主| Demeter法律簡介的詳細內容。更多資訊請關注PHP中文網其他相關文章!