[OOD]违反里氏替换原则的解决方案
关于OOD中的里氏替换原则,大家耳熟能祥了,不再展开,可以参考设计模式的六大设计原则之里氏替换原则。这里尝试讨论常常违反的两种形式和解决方案。
违反里氏替换原则的根源是对子类及父类关系不明确。我们在设计继承关系常常受一些主观认识的左右,比如Robert C. Martin提到的线段与线的关系,以及被大家说到烂的正方形与矩形。从以前的经验我们认为它们符合继承关系,比如线段是线的较短形式,正方形是矩形的一个特例。但事实上它们并不能完全的包容和替代。
以集合的形式表示,左图是里氏替换的目标,子类可以完全包容了父类的特性集合。右图则是说两者存在不兼容的特性集合:
对应的解决方案就是进一步抽象,将它们之前的关系从语言的角度重新定义,也许果真是is-a, 也许是has-a,也许它们只是兄弟。
基本的思路如下:
1. 找到更高层次的抽象
以Robert C. Martin举的线与线段为例, 初始实现Line是LineSegment的基类:
// Line代表经过两点(P1,P2)的一条的直线
class Line{
public:
double GetSlope() const;
Point GetP1() const;
Point GetP2() const;
virtual bool IsOn(const Point&) const; private:
Point itsP1;
Point itsP2;
} // LineSegment则是由两点(P1,P2)连接的线段。
class LineSegment : public Line {
public:
virtual bool IsOn(const Point&) const;
}
其中IsOn函数用于计算某个点在不在直线或线段上。对于直线而言,一个点在不在其上仅仅取决于这个点相对于直线的两个点的关系。而对于线段而言,还是它是否在线程起止边界内。两者对于这个接口函数的判断条件并不相同,所以LineSegment无法直接代替父类,违反了里氏替换原则。
解决方案是将这个不一致的接口排除掉,剩下的公共接口做为直线和线段的基类,即定义一个LinearObject做为Line及LineSegment的基类:
class Line{
public:
double GetSlope() const;
Point GetP1() const;
Point GetP2() const;
// 纯虚函数的意义在于,确保使用基类的客户代码不会使用这个接口函数
virtual bool IsOn(const Point&) const = ;
private:
Point itsP1;
Point itsP2;
}
2. 改为has-a关系
另一种解决方案,是针对继承关系太过牵强的情况,比如所谓的is-implemented-in-terms-of (由谁实现)的情况,不如转化为组合模式,如下面的关系:
Scott Meyers在Effective C++ 3e, Item 38提到一个案例。比如准备基于std::list实现一个Set。初步想法是期望保持与list相同的接口,于是定义为:
template<typename T>
class Set : public std::List<T> {...}
但Set与List在行为有一个巨大的差异是Set不允许重复的元素,所以也违反了里氏替换原则。
解决方案就是,使用std::list实现,就是一个has-a关系,可以定义为:
template<typename T>
class Set {
public:
void insert(const T& item);
void remove(const T& item);
... private:
std::list<T> rep;
}
[OOD]违反里氏替换原则的解决方案的更多相关文章
- 【设计模式六大原则2】里氏替换原则(Liskov Substitution Principle)
肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的. 定义1:如果对 ...
- 里氏替换原则(Liskov Substitution Principle,LSP)
肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.其实原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的. 定义1:如果对每一 ...
- ZT 设计模式六大原则(2):里氏替换原则
设计模式六大原则(2):里氏替换原则 分类: 设计模式 2012-02-22 08:46 23330人阅读 评论(41) 收藏 举报 设计模式class扩展string编程2010 肯定有不少人跟我刚 ...
- 设计模式六大原则(2):里氏替换原则(Liskov Substitution Principle)
肯定有不少人跟我刚看到这项原则的时候一样,对这个原则的名字充满疑惑.事实上原因就是这项原则最早是在1988年,由麻省理工学院的一位姓里的女士(Barbara Liskov)提出来的. 定义1:假设对每 ...
- C# 实例解释面向对象编程中的里氏替换原则
在面向对象编程中,SOLID 是五个设计原则的首字母缩写,旨在使软件设计更易于理解.灵活和可维护.这些原则是由美国软件工程师和讲师罗伯特·C·马丁(Robert Cecil Martin)提出的许多原 ...
- 【C#设计模式】里氏替换原则
今天,我们再来学习 SOLID 中的"L"对应的原则:里式替换原则. 里氏替换原则 里氏替换原则(Liskov Substitution Principle):派生类(子类)对象能 ...
- 第2章 面向对象的设计原则(SOLID):2_里氏替换原则(LSP)
2. 里氏替换原则(Liskov Substitution Principle,LSP) 2.1 定义 (1)所有使用基类的地方必须能透明地使用子类替换,而程序的行为没有任何变化(不会产生运行结果错误 ...
- 里氏替换原则(Liskov Substitution Principle)
开放封闭原则(Open Closed Principle)是构建可维护性和可重用性代码的基础.它强调设计良好的代码可以不通过修改而扩展,新的功能通过添加新的代码来实现,而不需要更改已有的可工作的代码. ...
- 2.里氏替换原则(Liskov Substitution Principle)
1.定义 里氏替换原则的定义有两种,据说是由麻省理工的一位姓里的女士所提出,因此以其名进行命名. 定义1:如果对一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1所定义的程序P中在o1全都 ...
随机推荐
- php生成txt文件换行问题
用双引号即"\r\n"换行,不能用单引号即'\r\n'.
- oracle数据库一些问题
1.oracle12安装的过程中需要注意字符的选择(特别注意 选择ZHS16GBK 国内企业大多选这个 否则创建数据的时候会出现中文变成问号) 2.设置密码是必须是大小写混合的 pl/sql32链 ...
- Enum(枚举)示例
package main; public class EnumTest { /** * 普通枚举 */ public enum ColorEnum { red, g ...
- Jquery-------获取网页参数
看如下代码: function getURLParameter(name) { return decodeURI( (RegExp(name + '=' + '(.+?)(&|$)').exe ...
- 学习KnockOut第一篇之Hello World
学习KnockOut第一篇之Hello World 笔者刚开始学习KnockOut.写的内容就相当于一个学习笔记.且在此处向官网致敬,比较喜欢他们家的Live Example版块,里面有jsFiddl ...
- 工程移除CocoaPods依赖库
http://zanderzhang.gitcafe.io/2015/09/26/工程移除CocoaPods依赖库/ 点这里--->CocoaPods安装和使用教程 当我们工程安装很多第三方开源 ...
- 虚拟机备份转移后,网络启动异常,提示“SIOCSIFADDR: No such device”的解决方案
虚拟机管理软件:Oracle VirturalBox Manager 4.0.8 虚拟机:Ubuntu Server 10.10 i386 The problem lies in the fact t ...
- 1515 跳 - Wikioi
题目描述 Description邪教喜欢在各种各样空间内跳.现在,邪教来到了一个二维平面.在这个平面内,如果邪教当前跳到了(x,y),那么他下一步可以选择跳到以下4个点:(x-1,y), (x+1,y ...
- QT模态弹出对话框
QDialog QWidget 默认show()都是非模态 如果需要模态显示, QDialog ==> setModal(true); show(); exec(); QWidget ==> ...
- Atmel Studio 6.0新建项目
使用Atmel Studio 6.0新建一个项目 连接Atmel 开发板与调试板,开发板使用的芯片是ATXMGEA128A1 注意: 以上对于开发板与调试板的连接,需要非常注意连接时线的方向, ...