首頁 > 後端開發 > php教程 > PHP主| Demeter法律簡介

PHP主| Demeter法律簡介

Christopher Nolan
發布: 2025-02-25 23:26:10
原創
136 人瀏覽過

PHP Master | Introduction to the Law of Demeter

核心要點

  • 迪米特法則,也稱為最少知識原則,提倡最小化對像對其他對象的了解,主張僅與直接鄰居交互,以增強模塊化和可維護性。
  • 遵守迪米特法則可以顯著提升松耦合軟件模塊的設計,使代碼更易於維護、測試和修改。
  • 迪米特法則的常見違反情況發生在對像或方法對其他對象的結構和元素了解過多時,導致代碼緊密耦合,難以管理和演進。
  • PHP 中的實際示例演示了違規行為如何巧妙地嵌入常見實踐中,例如使用暴露其他對象內部細節的服務定位器。
  • 將代碼重構為符合迪米特法則,需要僅與必要的組件進行直接交互,避免不必要的中間體,這些中間體會使架構複雜化並增加依賴性。
  • 雖然迪米特法則通過減少依賴性和促進高內聚性來提高代碼質量,但應務實地應用它,考慮具體的上下文和復雜性和性能方面的潛在權衡。

軟件編程是藝術(有時是即興創作的委婉說法)和許多行之有效的啟發式方法的平衡組合,用於解決某些問題並以體面的方式解決它們。幾乎沒有人會不同意,藝術方面是迄今為止最難磨練和提煉的方面。另一方面,駕馭啟發式方法背後的力量對於能夠開發基於良好設計的軟件至關重要。由於有如此多的啟發式方法說明軟件系統應該如何以及為什麼應該堅持特定方法,因此在 PHP 世界中沒有看到更廣泛地實施它們,這令人相當失望。例如,迪米特法則可能是該語言領域中最被低估的法則之一。實際上,該法則的“只與你的密友交談”的格言在 PHP 中似乎還處於相當不成熟的狀態,這導致了幾個面向對象代碼庫整體質量的下降。一些流行的框架正在積極推動它向前發展,試圖更加遵守該法則的戒律。為違反迪米特法則而互相指責是沒有意義的,因為減輕此類破壞的最佳方法是簡單地採取務實態度,並了解該法則下的實際內容,從而在編寫面向對象代碼時有意識地應用它。為了加入正義事業,並從實踐的角度更深入地研究該法則,在接下來的幾行中,我將通過一些實踐示例來演示,為什麼像遵守該法則的原則這樣簡單的事情在設計松耦合軟件模塊時可以真正提升效率。

了解過多並非好事

通常被稱為最少知識原則,迪米特法則所推崇的規則很容易理解。簡單地說,假設您有一個精心設計的類,它實現了一種給定的方法,那麼該方法應該被限制為調用屬於以下對象的其他方法:

  1. 方法的原始類的實例。
  2. 目標方法的參數對象。
  3. 目標方法創建的對象。
  4. 方法的原始類的依賴項對象。
  5. 原始類可以在目標方法中訪問的全局對象(哎呀!)

儘管該列表遠非正式(對於更正式的列表,請查看維基百科),但這些要點很容易理解。在傳統設計中,一個對像對另一個對象了解太多(這隱含地包括知道如何訪問第三個對象)被認為是錯誤的,因為在某些情況下,對象必須不必要地從上到下遍歷笨拙的中間體才能找到其需要按預期工作所需的實際依賴項。出於顯而易見的原因,這是一個嚴重的設計缺陷。調用者對中間體的內部結構有相當廣泛和詳細的了解,即使這是通過幾個 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中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板