谈谈蛋疼的问题:里式替换原则:正方形是长方形吗?

最近听到有人说,根据里式替换原则,正方形不是长方形。理由如下:

class Rectangle
{
public:
    void setLength(int length) { m_nLength = length;}
    void setWidth(int width) { m_nWidth = width; }
    int area() { return m_nWidth * m_nLength; }
private:
    int m_nLength;
    int m_nWidth;
};

class Square : public Rectangle
{
public:
    void setLength(int length) { m_nSide = length;}
    void setWidth(int width) { m_nSide = width; }
    int area() { return m_nSide * m_nSide; }
private:
    int m_nSide;
};

void testArea(Rectangle *rec)
{
    rec->setLength(3);
    rec->setWidth(4);
    Q_ASSERT(12 == rec->area());
}

在定义上,子类不能比父类更严格,更严格就不适用于里式替换原则了。
里式替换原则的定义如下:
    Liskov于1987年提出了一个关于继承的原则“Inheritance should ensure that any property proved about supertype objects also holds for subtype objects.”——“继承必须确保超类所拥有的性质在子类中仍然成立。”也就是说,当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。


从定义中可以看出来,的确,对于C++中的继承关系,(is-A关系),要求子类只能比父类更宽松,不能更严格。才能保证父类所有的性质在子类中都成立。
        保持所有引用基类的地方必须能透明地使用其子类的对象
我们平时在数学中说的“正方形是特殊的长方形”,更精确的理解应该是“正方形属于长方形”,而不是“正方形是长方形”,当然中文中可以理解成一个意思,英文不同,is-a和beyond to是两个不同的意思。
延伸一下,“属于”可以说“正方形是长方形”,又别扭了,因为主体变了,后者“长方形”不具备拥有属性,虽然前者具备属于属性。
有人说:为了保证里式替换原则,也就是保证继承的纯粹性,就要做到:
1:子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。
2:子类中可以增加自己特有的方法。
3:当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
4:当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。


第3,4条,作一特别说明:
在程序中确保调用父类的地方,换成子类后,调用的方法不变。
    【虚方法当然一直会调用子类的。但是如果是子类隐藏了父类的方法呢?静态调用就会导致替换后调用的方法不一致。为了处理隐藏这种情况,就需要保证3成立,如果方法名一样,但是子类形参更宽松,就会首先匹配父类,】实际中,我并没有测到这样的情况,实际的调用是根据函数的调用主体的静态类型决定调用哪个函数,而不是严格程度!!
所以3,4就忽略吧。
程序如下:

#include <iostream>
using namespace std;

class Base
{
};

class Derived : public Base
{
};

class Rectangle
{
public:
    virtual void show(Base *p) { cout << "Father" << endl; }
};

class Square : public Rectangle
{
public:
    //using Rectangle::show;
    virtual void show(Derived *p) { cout << "Son" << endl; }
};

int main()
{
    Base d;
    // Derived d; 两个都能调,
    Square *pRec = new Square;
    //Square *pRec = new Square;
    pRec->show(&d);
    delete pRec;

    /*
     *      |----Rectangle   Father
     *Base--|
     *      |----Square      (Father)本来应该根据类的静态类型来调用,但类型不匹配,编译不过,如果引入父类,调用的就是父类的方法
     *
     *           |-----Rectangle Father 子类并没有覆盖父类函数,根据类静态类型调用
     * Derived---|
     *           |-----Square  Son
     *
     * 总之是根据函数的调用主体的静态类型决定调用哪个函数。
     */
    return 0;
}
        如果子类隐藏了父类的方法,实现不一样,定然会根据对象的静态类型调用,也就定然违反了里式替换原则。
总之:里式替换原则就是要记住,多态+子类比父类更宽松(可以有自己特有的方法)
蛋疼的问题讲完,似乎饶了一大圈,有回到了原点,接下来,我来讲个古代类似的故事。
想这个问题之前,我想到了古代公孙龙说过的“白马非马”的论点。他曾经跟孔子的6世孙 孔穿 就这个问题辩论不落下风。
后来被邹衍一席话说的羞愧难当,白马非马究竟是怎样的理论,读者可以自己百度,公孙龙与邹衍的经过是怎样的呢?邹衍又说了什么呢?
        齐邹衍过赵,平原君使与公孙龙论白马非马之说。邹子曰:「不可。夫辩者,别殊类使不相害,序异端使不相乱。抒意通指,明其所谓,使人与知焉,不务相迷也。故胜者不失其所守,不胜者得其所求。若是,故辩可为也。及至烦文以相假,饰辞以相敦,巧譬以相移,引人使不得及其意,如此害大道。夫缴纷争言而竞后息,不能无害君子,衍不为也。」座皆称善。公孙龙由是遂绌。
翻译如下:
邹衍路过赵国,平原君让他和公孙龙辩论“白马非马”的观点。邹衍说:“不行。所谓辩论,应该区别不同类型,不相侵害;排列不同概念,不相混淆;抒发自己的意旨和一般概念,表明自己的观点,让别人理解,而不是困惑迷惘。如此,辩论的胜者能坚持自己的立场,不胜者也能得到他所追求的真理,这样的辩论是可以进行的。如果用繁文缛节来作为凭据,用巧言饰辞来互相诋毁,用华丽词藻来从偷换概念,吸引别人使之不得要领,就会妨害治学的根本道理。那种纠缠不休,咄咄逼人,总要别人认输才肯住口的作法,有害君子风度,我邹衍是绝不参加的。”在座的人听罢都齐声叫好。从此,公孙龙便受到了冷落 。


    可见,白马非马,无理取闹而已。


阅读更多
个人分类: 技术总结
上一篇C++ 内连接外链接的问题
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭