核心要点
- 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是一种服务器端脚本语言,用于动态网页开发和服务器端应用程序。1.PHP是一种解释型语言,无需编译,适合快速开发。2.PHP代码嵌入HTML中,易于网页开发。3.PHP处理服务器端逻辑,生成HTML输出,支持用户交互和数据处理。4.PHP可与数据库交互,处理表单提交,执行服务器端任务。

PHP在过去几十年中塑造了网络,并将继续在Web开发中扮演重要角色。1)PHP起源于1994年,因其易用性和与MySQL的无缝集成成为开发者首选。2)其核心功能包括生成动态内容和与数据库的集成,使得网站能够实时更新和个性化展示。3)PHP的广泛应用和生态系统推动了其长期影响,但也面临版本更新和安全性挑战。4)近年来的性能改进,如PHP7的发布,使其能与现代语言竞争。5)未来,PHP需应对容器化、微服务等新挑战,但其灵活性和活跃社区使其具备适应能力。

PHP的核心优势包括易于学习、强大的web开发支持、丰富的库和框架、高性能和可扩展性、跨平台兼容性以及成本效益高。1)易于学习和使用,适合初学者;2)与web服务器集成好,支持多种数据库;3)拥有如Laravel等强大框架;4)通过优化可实现高性能;5)支持多种操作系统;6)开源,降低开发成本。

PHP没有死。1)PHP社区积极解决性能和安全问题,PHP7.x提升了性能。2)PHP适合现代Web开发,广泛用于大型网站。3)PHP易学且服务器表现出色,但类型系统不如静态语言严格。4)PHP在内容管理和电商领域仍重要,生态系统不断进化。5)通过OPcache和APC等优化性能,使用OOP和设计模式提升代码质量。

PHP和Python各有优劣,选择取决于项目需求。1)PHP适合Web开发,易学,社区资源丰富,但语法不够现代,性能和安全性需注意。2)Python适用于数据科学和机器学习,语法简洁,易学,但执行速度和内存管理有瓶颈。

PHP用于构建动态网站,其核心功能包括:1.生成动态内容,通过与数据库对接实时生成网页;2.处理用户交互和表单提交,验证输入并响应操作;3.管理会话和用户认证,提供个性化体验;4.优化性能和遵循最佳实践,提升网站效率和安全性。

PHP在数据库操作和服务器端逻辑处理中使用MySQLi和PDO扩展进行数据库交互,并通过会话管理等功能处理服务器端逻辑。1)使用MySQLi或PDO连接数据库,执行SQL查询。2)通过会话管理等功能处理HTTP请求和用户状态。3)使用事务确保数据库操作的原子性。4)防止SQL注入,使用异常处理和关闭连接来调试。5)通过索引和缓存优化性能,编写可读性高的代码并进行错误处理。

在PHP中使用预处理语句和PDO可以有效防范SQL注入攻击。1)使用PDO连接数据库并设置错误模式。2)通过prepare方法创建预处理语句,使用占位符和execute方法传递数据。3)处理查询结果并确保代码的安全性和性能。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

VSCode Windows 64位 下载
微软推出的免费、功能强大的一款IDE编辑器

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

SublimeText3 Linux新版
SublimeText3 Linux最新版

Dreamweaver CS6
视觉化网页开发工具

螳螂BT
Mantis是一个易于部署的基于Web的缺陷跟踪工具,用于帮助产品缺陷跟踪。它需要PHP、MySQL和一个Web服务器。请查看我们的演示和托管服务。