敏捷软件开发 - 原则、模式与实践 —— 敏捷设计(四)里氏替换原则

本文为敏捷软件开发 - 原则、模式与实践系列的一部分。

本文对应原书第10章。

里氏替换原则(LSP - The Liskov Substitution Principle)

子类型必须能够替换掉它们的基类型。

问题

对于LSP的违反常常会导致以明显违反OCP的方式使用运行时类型辨别。这种方式常常是使用一个显示的if语句或者if/else链去确定一个对象的类型,以便于可以选择针对该类型的正确行为。

我们经常说继承是IS-A关系。也就是说,如果一个新类型的对象被认为和一个已有类的对象之间满足IS-A关系,那么这个新对象的类应该从这个已有对象的类派生。IS-A关系的这种用法有时被认为是面向对象分析基本技术之一。一个正方形是一个矩形,所以正方形类就应该派生自矩形类。不过,这种想法会带来一些微妙但极为值得重视的问题。

矩形的长和宽不相同,而正方形的长和宽相同。考虑一个计算矩形面积的函数,设定矩形长为l,宽为w,并且l<>w,并断定矩形面积为l*w。对于这个函数,从矩形类继承的正方形类是无法满足的。

LSP让我们得出一个非常重要的结论:一个模型,如果孤立地看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来表现。在考虑一个特定设计是否恰当时,不能完全孤立地来看这个解决方案。必须要根据该设计的使用者所做出的合理假设来审视它。大多数这样的假设都很难预测。事实上,如果试图去预测所有这些假设,我们所得到的系统很可能会充满不必要的复杂性的臭味。因此,像所有其它原则一样,通常最好的方式是只预测那些最明显的对于LSP的违反情况而推迟所有其它的预测,直到出现相关的脆弱性的臭味时,才去处理它们。

基于契约设计

LSP清楚地指出,OOD中IS-A关系是就行为方式而言的,行为方式是可以进行合理假设的,是客户程序所依赖的。怎样才能知道客户真正的要求呢?有一项技术可以使这些合理的假设明确化,从而支持LSP。这项技术被称为基于契约设计。可以通过编写单元测试的方式来指定契约。单元测试通过彻底地测试一个类的行为来使该类的行为更加清晰。客户代码的编写者会去查看这些单元测试,这样他们就可以知道对于要使用的类,应该做出什么合理的假设。

简单方案

有一个简单的方案可以解决违反LSP的问题,该方案也阐明了一个OOD的重要工具。可以把这两个类的公共部分提取出来作为一个抽象基类。

启发规则

有一些简单的启发规则可以提供一些有关违反LSP的提示。这些规则都和以某种方式从其基类中去除功能的派生类有关。完成的功能少于基类的派生类通常是不能替换其基类的,因此就违反了LSP。

  • 派生类中有退化函数
  • 从派生类中抛出异常

结论

OCP是OOD中很多说法的核心。如果这个原则应用得有效,应用程序就会具有更多的可维护性、可重用性以及健壮性。LSP是使OCP成为可能的主要原则之一。正是子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。因此,如果没有显式地强制基类类型的契约,那么代码就必须良好地并且明显地表达出这一点。

术语“IS-A”的含义过于宽泛以至于不能作为子类型的定义。子类型的正确定义是“可替换性的”,这里的可替换性可以通过显式或者隐式的契约来定义。

完整内容请查看敏捷软件开发 - 原则、模式与实践系列

你可能感兴趣的:(敏捷软件开发 - 原则、模式与实践 —— 敏捷设计(四)里氏替换原则)