设计原则中被冤枉的正方形与长方形

在这里插入图片描述

我们先来看看里氏替换原则LSP的描述。LSP可以简单地描述为:把父类对象替换成它的子类对象,程序的行为没有变化。注意这里的重点,是 “行为没有变化”

《架构整洁之道》第9章提到“正方形/长方形问题是一个著名(或者说臭名远扬)的违反LSP的设计案例”。

那么,正方形/长方形问题真的违反LSP设计吗?

从数学上看,正方形是一种特殊的长方形,这是没有异议的。一般说A是B的时候,A和B是有继承关系的。放到面向对象语言上来说,正方形也应该归属于长方形,即正方形Square类是长方形Rectangle类的子类。

为了说明,给出Rectangle类和Square类的定义:

class Rectangle {  
    int width;  
    int height;  
  
    public Rectangle() {  
    }  
  
    public void setW(int w) {  
        width = w;  
    }  
  
    public void setH(int h) {  
        height = h;  
    }  
  
    public int getW() {  
        return width;  
    }  
  
    public int getH() {  
        return height;  
    }  
    
    public int area(){  
        return width * height;  
    }  
}  
  
class Square extends Rectangle {  
    int side;  
  
    public void setS(int s) {  
        height = width = side = s;
    }  
  
    public int getS() {  
        return side;  
    }  

	@Override
    public void setW(int w) {  
        setS(w);  
    }  

	@Override
    public void setH(int h) {  
        setS(h);  
    }  
}

从代码中可以看到,Rectangle的行为是可以设置和获取高和宽,还有就是可以根据高和宽计算面积。如果用Square的对象替换Rectangle的对象,那么程序的行为没有变化。因为对象还是可以设置和获取高和宽,还是可以根据高和宽计算面积。

既然如此,为什么《架构整洁之道》的作者觉得正方形/长方形问题违反LSP设计?他说错了吗?错在哪里?

《架构整洁之道》第9章中证明正方形/长方形问题违反LSP设计的解释如下:

Square类并不是Rectangle类的子类型,因为Rectangle类的高和宽可以分别修改,而Square类的高和宽则必须一同修改。由于User类始终认为自己在操作Rectangle类,因此会带来一些混淆。例如在下面的代码中:
Rectangle r = …
r.setW(5);
r.setH(2);
assert(r.area()== 10);
很显然,如果上述代码在…处返回的是Square类,则最后的这个assert是不会成立的。

我们先来看看关于assert的问题,作者说 “如果上述代码在…处返回的是Square类,则最后的这个assert是不会成立的”。不会成立是正常的,因为这个断言的逻辑是有问题的。正确的断言逻辑是assert (r.area() == r.getW() * r.getH());。这样的话,不管r对象是Rectangle类还是Square类,都是没有问题的。

那么作者的断言逻辑问题出在哪?为什么不能用10来断言?因为这里的10是程序行为的结果,而不是表示程序的行为。本文开头就强调了,LSP的重点是 “行为没有变化”,而不是 “行为结果没有变化” 。为什么不能用行为结果来断言呢?因为对象的多态性,多态性让不同对象的相同行为可能产生不同的结果。你不能要求子类的行为结果和父类的行为结果是一样的。

我们再来看看正方形高和宽同时修改的问题。作者说“因为Rectangle类的高和宽可以分别修改,而Square类的高和宽则必须一同修改”,看起来似乎是这么回事儿,正方形改变了长方形的行为。但是“能否分别修改高和宽”是不能作为判断是否是长方形的条件的,你不能说“一个不能分别修改宽和高的长方形”不是长方形。

如果你非要说,只要有行为改变了,就违反LSP。那按照这个思路,就不单是正方形与长方形的问题了,请看下面的DoubleWidthRectangle类:

class DoubleWidthRectangle extends Rectangle {  
    @Override  
    public void setW(int w) {  
        super.setW(w * 2);  
    }  
}

这个DoubleWidthRectangle类没有不能分别设置高和宽的问题,只是设置宽的时候会自动乘2。明显如果按照上面提到的思路的话,这个类设置宽时会自动乘2改变了Rectangle类的行为,那么DoubleWidthRectangle类违反了LSP设计,不是Rectangle的子类。

以此类推,将会得出一个结论:“所有重写了父类中有具体实现的行为的子类都是违反LSP设计的”。明显这个结论是有问题的。

至此得证,正方形/长方形问题并没有违反LSP设计,Square类是Rectangle类的子类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值