李氏替换原则(Liskov Substitution Principle: LSP)

子类型必须能够替换掉他们的父类型

这里的所有观点摘抄自《敏捷软件开发原则、模式与实践》,原著Robert C. Martin,邓辉等译。

李氏替换原则

假设有一个函数f,接受一个指向某个基类B的指针或者引用。如果把B的派生类D的对象作为参数传给f,会导致f出现错误行为。那么D就违反了LSP。

另外一种情况,假定要对传入的对象D做测试,看D是否满足f所需要的条件。这个测试就会违反OCP。原因是什么呢?因为f需要的行为是B所具有的行为。
但是对于B的派生类却没有这样的要求。要让测试D的用例通过,就必须修改f,这样就违反了对修改封闭的原则,即OCP。

看一个违反LSP的例子:

struct Point (double x, y);

struct Shape {
    enum ShapeType { square, circle } itsType;
    Shape(ShapeType t) : itsType(t) {}
}

struct Circle: public Shape
{
    Circle(): Shape(circle) {};
    void Draw() const;
    Point itsCenter;
    double itsRadius;
}

struct Square: public Shape
{
    Square(): Shape(square) {};
    void Draw() const;
    Point itsTopLeft;
    double itsSide;
}

void DrawShape(const Shape& s)
{
    if (s.itsType == Shape::square)
        static_cast<const Square&>(s).Draw();
    else if (s.itsType == Shape::circle)
        static_cast<const Circle&>(s).Draw();
}

很明显的,上述代码违反了OCP原则,因为我们新增一种形状,都会导致源代码的修改,需要添加新的if else。

这个例子也理所当然的违反了LSP,因为派生类Circle和Square并不能替换掉Shape. 除非Shape中包含Draw并且将Draw设置为虚函数。

另外一个有名的例子就是矩形和正方形的问题。我们在数学中会把正方形当做矩形的一个特例。从而影响到我们在开发过程中,也会理所当然的
认为正方形应该是从矩形派生而来。

但实际上应该考虑清楚,正方形和矩形,并非是一个继承的关系。矩形的长宽可以独立调整,如果正方形派生自矩形,那么正方形的长宽也应该可以
独立的调整。但实际上正方形长宽是固定相等的。

有人可能会说,可以在正方形的类中特殊处理。例如:

void Square::SetWidth(double w)
{
    Rectangle::SetWidth(w);
    Rectangle::SetHeight(w);
}

void Square::SetHeight(double w)
{
    Rectangle::SetWidth(w);
    Rectangle::SetHeight(w);
}

class Rectangle
{
    public:
        virtual void SetWidth(double w);
        virtual void SetHeight(double w);
}

void f(Rectangle& r)
{
    r.SetWidth(23);
}

上面的代码看起来运行没有问题,Square类的对象也可以作为参数传入f,但实际上隐藏了本质上的问题。也就是认知上的问题。

考虑如下的代码:

void ch(Rectange& r)
{
    r.SetWidth(22);
    r.SetHeight(30);
}

如果传入了一个正方形的对象,我们最终期望的正方形边长应该是多大?使用者只会知道,可以传入正方形,并期望传入之后
满足矩形的行为。但实际上会出现令人迷惑的结果。

这个有点类似于挂羊头卖狗肉的感觉,实际上需要的羊肉的味道,但是你给的确实狗肉,出来的结果可想而知。

如何识别

经过上面的几个例子,可以看到,如果单单从一个问题来看,也许系统是满足条件的。违反不违反LSP并没有多大的坏处。但是,
从设计的使用者的角度,就可以看出,违反了LSP原则会给系统的稳定性带来多大的影响。

但是有谁能够知道设计的使用者会做出怎样的合理假设呢?大多数这样的假设都很难预测。事实上如果试图去猜测使用者的所有
假设,会让系统无比复杂。最好的方法就是只预测那些明显对于LSP违反的情况。

继续深入的思考一下,LSP所描述的是一个严格的IS-A的关系。我们从认知上来看,正方形似乎是一个矩形,是它的一个特例。
但是从使用者的角度,正方形却处处和长方形不兼容。那么真正可以区分是否满足LSP(也就是满足IS-A)的条件是什么呢?

  • 对象的行为方式

因为正方形和长方形所预期的行为方式不相容,所以不满足LSP。

所以,考察一个继承关系是否满足LSP,最终看的是派生的类和基类是否具有相同的行为方式。

具体的考察行为方式的办法有两种:

  • 基于契约的设计(DBC)
  • 单元测试中指定契约
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容

  • 《Agile Principles, Patterns, and Practices in C#》by Micah...
    丑小丫大笨蛋阅读 11,874评论 3 18
  • 面向对象的3个基本要素: 封装、继承、多态 面向对象的5个基本设计原则: 单一职责原则(Single-Respos...
    badcyc阅读 846评论 0 4
  • 这是SOLID原则这一系列的第四篇文章,主要来描述里氏替换(LSP)原则。LSP指定所有引用基类的地方必须能透明地...
    好大一只龍阅读 774评论 0 0
  • 六月的一个周一,大概中午11:45左右,在我家小区大门口,我遇见了一个小孩子。 是个男孩儿,估计是建安小学的小学生...
    一场春雪阅读 221评论 0 0
  • 从官网上下载安装之后,JDK默认安装路径:/Library/Java/JavaVirtualMachines/jd...
    大玩具阅读 2,020评论 0 2