Liskov替代原則
核心要點
- Liskov 替換原則 (LSP) 是面向對象編程中的一個關鍵概念,它確保子類可以替換其基類抽象,而不會破壞與客戶端代碼的契約。它維護系統設計的完整性,對於代碼的可重用性至關重要。
- 在子類中重寫方法時,必須滿足某些要求:其簽名必須與父類的簽名匹配;其前提條件必須相同或更弱;其後置條件必須相同或更強;異常(如果有)必須與父類拋出的異常類型相同。
- 違反 LSP 會導致難以追踪的意外行為和錯誤。它還會使代碼更難維護和擴展,因為子類可以替換其超類的假設不再成立。
- 方法重寫並不總是違反 LSP。但是,如果重寫的方法以超類契約中未預期的方式改變了原始方法的行為,則會違反 LSP。
- 為了確保代碼符合 LSP,最好創建僅擴展(而不是重寫)其基類功能的子類。此外,使用組合而不是繼承以及實現接口可以幫助創建派生類不會破壞 LSP 施加的條件的抽象。
虛構場景:黑客與矩陣
以下對話來自《黑客帝國》三部曲的一個被刪減的場景:
墨菲斯:尼奧,我現在就在矩陣裡。很抱歉要告訴你這個壞消息,但我們的特工追踪 PHP 程序需要快速更新。它目前使用 PDO 的 query() 方法(帶字符串)從我們的數據庫中獲取所有矩陣特工的狀態,但我們需要改用預處理查詢。
尼奧:聽起來不錯,墨菲斯。我能拿到程序的副本嗎?
墨菲斯:沒問題。克隆我們的倉庫,看看 AgentMapper.php 和 index.php 文件。
(尼奧執行一些 Git 命令,以下代碼出現在他眼前)
<?php namespace ModelMapper; class AgentMapper { protected $_adapter; protected $_table = "agents"; public function __construct(PDO $adapter) { $this->_adapter = $adapter; } public function findAll() { try { return $this->_adapter->query("SELECT * FROM " . $this->_table, PDO::FETCH_OBJ); } catch (Exception $e) { return array(); } } }
<?php use ModelMapperAgentMapper; // 一个 PSR-0 兼容的类加载器 require_once __DIR__ . "/Autoloader.php"; $autoloader = new Autoloader(); $autoloader->register(); $adapter = new PDO("mysql:dbname=Nebuchadnezzar", "morpheus", "aa26d7c557296a4e8d49b42c8615233a3443036d"); $agentMapper = new AgentMapper($adapter); $agents = $agentMapper->findAll(); foreach ($agents as $agent) { echo "Name: " . $agent->name . " - Status: " . $agent->status . "<br>"; }
尼奧:墨菲斯,我剛拿到文件。我將子類化 PDO 並重寫它的 query() 方法,以便它可以使用預處理查詢。由於我的超能力,我應該能夠很快完成這個工作。保持冷靜。
(電腦鍵盤的敲擊聲迴盪在空氣中)
尼奧:墨菲斯,子類已經準備好測試了。隨時檢查一下。
(墨菲斯在他的筆記本電腦上快速搜索,看到了下面的類)
<?php namespace LibraryDatabase; class PdoAdapter extends PDO { protected $_statement; public function __construct($dsn, $username = null, $password = null, array $driverOptions = array()) { // 检查是否传递了有效的 DSN if (!is_string($dsn) || empty($dsn)) { throw new InvalidArgumentException("The DSN must be a non-empty string."); } try { // 尝试创建一个有效的 PDO 对象并设置一些属性。 parent::__construct($dsn, $username, $password, $driverOptions); $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $this->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); } catch (PDOException $e) { throw new RunTimeException($e->getMessage()); } } public function query($sql, array $parameters = array()) { try { $this->_statement = $this->prepare($sql); $this->_statement->execute($parameters); return $this->_statement->fetchAll(PDO::FETCH_OBJ); } catch (PDOException $e) { throw new RunTimeException($e->getMessage()); } } }
墨菲斯:適配器看起來不錯。我馬上試試,看看我們的特工映射器是否能夠跟踪穿越矩陣的活動特工。祝我好運。
(墨菲斯猶豫了一下,運行之前的 index.php 文件,這次使用尼奧的傑作 PdoAdapter 類。然後,一聲尖叫!)
墨菲斯:尼奧,我相信你就是“救世主”!只是我的臉上出現了一個可怕的致命錯誤,消息如下:
<code>Catchable fatal error: Argument 2 passed to LibraryDatabasePdoAdapter::query() must be an array, integer given, called in path/to/AgentMapper on line (who cares?)</code>
(另一聲尖叫)
尼奧:出了什麼問題? !出了什麼問題? ! (更多的尖叫)
墨菲斯:我真的不知道。哦,史密斯探員現在要來抓我了! (通訊突然中斷。長時間的沉寂結束了對話,暗示墨菲斯措手不及,被史密斯探員嚴重傷害了。)
LSP 不代表懶惰、愚蠢的程序員
不必說,上面的對話是虛構的,但問題無疑是真實的。如果尼奧像他曾經那樣著名的黑客那樣,只學習了一兩件關於 Liskov 替換原則 (LSP) 的知識,史密斯探員就可以立即被追踪到。最重要的是,墨菲斯可以免受探員的惡意意圖。對他來說真是太可惜了。然而,在許多情況下,PHP 開發人員對 LSP 的看法與尼奧之前的看法幾乎一樣:LSP 不過是一個純粹主義者的理論原則,在實踐中幾乎沒有應用。但他們走錯了路。即使 LSP 的正式定義讓人眼花繚亂(包括我),但其核心是避免定義不明確的類層次結構,其中後代的行為與使用相同契約的基類抽像大相徑庭。簡單來說,LSP 規定,在子類中重寫方法時,必須滿足以下要求:
- 其簽名必須與父類的簽名匹配
- 其前提條件(接受什麼)必須相同或更弱
- 其後置條件(預期什麼)必須相同或更強
- 異常(如果有)必須與父類拋出的異常類型相同
現在,請隨意再次閱讀上面的列表(別擔心,我會等),您希望能夠明白為什麼這很有道理。回到示例中,尼奧的致命錯誤只是沒有保持方法簽名相同,從而破壞了與客戶端代碼的契約。為了解決這個問題,特工映射器的 findAll() 方法可以用一些條件語句(明顯的代碼異味)重寫,如下所示:
<?php namespace ModelMapper; class AgentMapper { protected $_adapter; protected $_table = "agents"; public function __construct(PDO $adapter) { $this->_adapter = $adapter; } public function findAll() { try { return $this->_adapter->query("SELECT * FROM " . $this->_table, PDO::FETCH_OBJ); } catch (Exception $e) { return array(); } } }
如果您心情好,嘗試重構後的方法,它會運行良好,無論使用的是原生 PDO 對像還是 PDO 適配器的實例。我知道這聽起來很粗糙,但這只是一個快速簡便的修復,它公然違反了開閉原則。另一方面,可以重構適配器的 query() 方法以匹配其重寫父類的簽名。但這樣做,LSP 陳述的所有其他條件也應該滿足。簡而言之,這意味著應該謹慎地進行方法重寫,並且只有在非常強烈的理由下才能進行。在許多用例中,假設無法使用接口,最好創建僅擴展(而不是重寫)其基類功能的子類。在尼奧的 PDO 適配器的情況下,這種方法將完美運行,並且絕對不會在任何級別破壞客戶端代碼。正如我剛才所說,還有一個更有效——但更激進——的解決方案,它利用了實現接口的好處。雖然之前的 PDO 適配器是通過繼承創建的,並且不可否認地違反了 LSP 的戒律,但缺陷實際上來自最初設計特工映射器類的方式。實際上,它從上到下依賴於具體的數據庫適配器實現,而不是依賴於接口定義的契約。而大型 OO 力量從古代就說,這總是一件壞事。那麼,上述解決方案將如何實現呢?
(剩餘部分與輸入文本類似,可以根據需要進行調整和精簡)
以上是Liskov替代原則的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

PHP中有四種主要錯誤類型:1.Notice:最輕微,不會中斷程序,如訪問未定義變量;2.Warning:比Notice嚴重,不會終止程序,如包含不存在文件;3.FatalError:最嚴重,會終止程序,如調用不存在函數;4.ParseError:語法錯誤,會阻止程序執行,如忘記添加結束標籤。

PHP和Python各有優勢,選擇依據項目需求。 1.PHP適合web開發,尤其快速開發和維護網站。 2.Python適用於數據科學、機器學習和人工智能,語法簡潔,適合初學者。

PHP在電子商務、內容管理系統和API開發中廣泛應用。 1)電子商務:用於購物車功能和支付處理。 2)內容管理系統:用於動態內容生成和用戶管理。 3)API開發:用於RESTfulAPI開發和API安全性。通過性能優化和最佳實踐,PHP應用的效率和可維護性得以提升。

在PHP中,應使用password_hash和password_verify函數實現安全的密碼哈希處理,不應使用MD5或SHA1。1)password_hash生成包含鹽值的哈希,增強安全性。 2)password_verify驗證密碼,通過比較哈希值確保安全。 3)MD5和SHA1易受攻擊且缺乏鹽值,不適合現代密碼安全。

HTTP請求方法包括GET、POST、PUT和DELETE,分別用於獲取、提交、更新和刪除資源。 1.GET方法用於獲取資源,適用於讀取操作。 2.POST方法用於提交數據,常用於創建新資源。 3.PUT方法用於更新資源,適用於完整更新。 4.DELETE方法用於刪除資源,適用於刪除操作。

PHP是一種廣泛應用於服務器端的腳本語言,特別適合web開發。 1.PHP可以嵌入HTML,處理HTTP請求和響應,支持多種數據庫。 2.PHP用於生成動態網頁內容,處理表單數據,訪問數據庫等,具有強大的社區支持和開源資源。 3.PHP是解釋型語言,執行過程包括詞法分析、語法分析、編譯和執行。 4.PHP可以與MySQL結合用於用戶註冊系統等高級應用。 5.調試PHP時,可使用error_reporting()和var_dump()等函數。 6.優化PHP代碼可通過緩存機制、優化數據庫查詢和使用內置函數。 7

PHP通過$\_FILES變量處理文件上傳,確保安全性的方法包括:1.檢查上傳錯誤,2.驗證文件類型和大小,3.防止文件覆蓋,4.移動文件到永久存儲位置。

在PHPOOP中,self::引用當前類,parent::引用父類,static::用於晚靜態綁定。 1.self::用於靜態方法和常量調用,但不支持晚靜態綁定。 2.parent::用於子類調用父類方法,無法訪問私有方法。 3.static::支持晚靜態綁定,適用於繼承和多態,但可能影響代碼可讀性。
