核心要點
- 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中文網其他相關文章!

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

PHP在現代化進程中仍然重要,因為它支持大量網站和應用,並通過框架適應開發需求。 1.PHP7提升了性能並引入了新功能。 2.現代框架如Laravel、Symfony和CodeIgniter簡化開發,提高代碼質量。 3.性能優化和最佳實踐進一步提升應用效率。

PHPhassignificantlyimpactedwebdevelopmentandextendsbeyondit.1)ItpowersmajorplatformslikeWordPressandexcelsindatabaseinteractions.2)PHP'sadaptabilityallowsittoscaleforlargeapplicationsusingframeworkslikeLaravel.3)Beyondweb,PHPisusedincommand-linescrip

PHP類型提示提升代碼質量和可讀性。 1)標量類型提示:自PHP7.0起,允許在函數參數中指定基本數據類型,如int、float等。 2)返回類型提示:確保函數返回值類型的一致性。 3)聯合類型提示:自PHP8.0起,允許在函數參數或返回值中指定多個類型。 4)可空類型提示:允許包含null值,處理可能返回空值的函數。

PHP中使用clone關鍵字創建對象副本,並通過\_\_clone魔法方法定制克隆行為。 1.使用clone關鍵字進行淺拷貝,克隆對象的屬性但不克隆對象屬性內的對象。 2.通過\_\_clone方法可以深拷貝嵌套對象,避免淺拷貝問題。 3.注意避免克隆中的循環引用和性能問題,優化克隆操作以提高效率。

PHP適用於Web開發和內容管理系統,Python適合數據科學、機器學習和自動化腳本。 1.PHP在構建快速、可擴展的網站和應用程序方面表現出色,常用於WordPress等CMS。 2.Python在數據科學和機器學習領域表現卓越,擁有豐富的庫如NumPy和TensorFlow。


熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

Dreamweaver CS6
視覺化網頁開發工具

Atom編輯器mac版下載
最受歡迎的的開源編輯器

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

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

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中