定義
In a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e. an object of type T may be substituted with any object of a subtype S) without altering any of the desirable properties of the program (correctness, task performed, etc.)
即:所有引用基類的地方必須能透明地使用其子類的對(duì)象,也就是說(shuō)子類對(duì)象可以替換其父類對(duì)象,而程序執(zhí)行效果不變。
定義的解讀
在繼承體系中,子類中可以增加自己特有的方法,也可以實(shí)現(xiàn)父類的抽象方法,但是不能重寫(xiě)父類的非抽象方法,否則該繼承關(guān)系就不是一個(gè)正確的繼承關(guān)系。
優(yōu)點(diǎn)
可以檢驗(yàn)繼承使用的正確性,約束繼承在使用上的泛濫。
代碼講解
在這里用一個(gè)簡(jiǎn)單的長(zhǎng)方形與正方形的例子講解一下里氏替換原則。
需求點(diǎn)
創(chuàng)建兩個(gè)類:長(zhǎng)方形和正方形,都可以設(shè)置寬高(邊長(zhǎng)),也可以輸出面積大小。
不好的設(shè)計(jì)
首先聲明一個(gè)長(zhǎng)方形類,然后讓正方形類繼承于長(zhǎng)方形。
長(zhǎng)方形類:
//================== Rectangle.h ==================
@interface Rectangle : NSObject
{
@protected double _width;
@protected double _height;
}
//設(shè)置寬高
- (void)setWidth:(double)width;
- (void)setHeight:(double)height;
//獲取寬高
- (double)width;
- (double)height;
//獲取面積
- (double)getArea;
@end
//================== Rectangle.m ==================
@implementation Rectangle
- (void)setWidth:(double)width{
_width = width;
}
- (void)setHeight:(double)height{
_height = height;
}
- (double)width{
return _width;
}
- (double)height{
return _height;
}
- (double)getArea{
return _width * _height;
}
@end
正方形類:
//================== Square.h ==================
@interface Square : Rectangle
@end
//================== Square.m ==================
@implementation Square
- (void)setWidth:(double)width{
_width = width;
_height = width;
}
- (void)setHeight:(double)height{
_width = height;
_height = height;
}
@end
可以看到,正方形類繼承了長(zhǎng)方形類以后,為了保證邊長(zhǎng)永遠(yuǎn)是相等的,特意在兩個(gè)set方法里面強(qiáng)制將寬和高都設(shè)置為傳入的值,也就是重寫(xiě)了父類Rectangle的兩個(gè)set方法。但是里氏替換原則里規(guī)定,子類不能重寫(xiě)父類的方法,所以上面的設(shè)計(jì)是違反該原則的。
而且里氏替換原則原則里面所屬:子類對(duì)象能夠替換父類對(duì)象,而程序執(zhí)行效果不變。我們通過(guò)一個(gè)例子來(lái)看一下上面的設(shè)計(jì)是否符合:
在客戶端類寫(xiě)一個(gè)方法:傳入一個(gè)Rectangle類型并返回它的面積:
- (double)calculateAreaOfRect:(Rectangle *)rect{
return rect.getArea;
}
我們先用Rectangle對(duì)象試一下:
Rectangle *rect = [[Rectangle alloc] init];
rect.width = 10;
rect.height = 20;
double rectArea = [self calculateAreaOfRect:rect];//output:200
長(zhǎng)寬分別設(shè)置為10,20以后,結(jié)果輸出200,沒(méi)有問(wèn)題。
現(xiàn)在我們使用Rectange的子類Square的對(duì)象替換原來(lái)的Rectange對(duì)象,看一下結(jié)果如何:
Square *square = [[Square alloc] init];
square.width = 10;
square.height = 20;
double squareArea = [self calculateAreaOfRect:square];//output:400
結(jié)果輸出為400,結(jié)果不一致,再次說(shuō)明了上述設(shè)計(jì)不符合里氏替換原則,因?yàn)樽宇惖膶?duì)象square替換父類的對(duì)象rect以后,程序執(zhí)行的結(jié)果變了。
不符合里氏替換原則就說(shuō)明該繼承關(guān)系不是正確的繼承關(guān)系,也就是說(shuō)正方形類不能繼承于長(zhǎng)方形類,程序需要重新設(shè)計(jì)。
我們現(xiàn)在看一下比較好的設(shè)計(jì)。
較好的設(shè)計(jì)
既然正方形不能繼承于長(zhǎng)方形,那么是否可以讓二者都繼承于其他的父類呢?答案是可以的。
既然要繼承于其他的父類,它們這個(gè)父類肯定具備這兩種形狀共同的特點(diǎn):有4個(gè)邊。那么我們就定義一個(gè)四邊形的類:Quadrangle。
//================== Quadrangle.h ==================
@interface Quadrangle : NSObject
{
@protected double _width;
@protected double _height;
}
- (void)setWidth:(double)width;
- (void)setHeight:(double)height;
- (double)width;
- (double)height;
- (double)getArea;
@end
接著,讓Rectangle類和Square類繼承于它:
Rectangle類:
//================== Rectangle.h ==================
#import "Quadrangle.h"
@interface Rectangle : Quadrangle
@end
//================== Rectangle.m ==================
@implementation Rectangle
- (void)setWidth:(double)width{
_width = width;
}
- (void)setHeight:(double)height{
_height = height;
}
- (double)width{
return _width;
}
- (double)height{
return _height;
}
- (double)getArea{
return _width * _height;
}
@end
Square類:
//================== Square.h ==================
@interface Square : Quadrangle
{
@protected double _sideLength;
}
-(void)setSideLength:(double)sideLength;
-(double)sideLength;
@end
//================== Square.m ==================
@implementation Square
-(void)setSideLength:(double)sideLength{
_sideLength = sideLength;
}
-(double)sideLength{
return _sideLength;
}
- (void)setWidth:(double)width{
_sideLength = width;
}
- (void)setHeight:(double)height{
_sideLength = height;
}
- (double)width{
return _sideLength;
}
- (double)height{
return _sideLength;
}
- (double)getArea{
return _sideLength * _sideLength;
}
@end
我們可以看到,Rectange和Square類都以自己的方式實(shí)現(xiàn)了父類Quadrangle的公共方法。而且由于Square的特殊性,它也聲明了自己獨(dú)有的成員變量_sideLength以及其對(duì)應(yīng)的公共方法。
注意,這里
Rectange和Square并不是重寫(xiě)了其父類的公共方法,而是實(shí)現(xiàn)了其抽象方法。
下面來(lái)看一下這兩個(gè)設(shè)計(jì)的UML 類圖,可以更形象地看出兩種設(shè)計(jì)上的區(qū)別:
UML 類圖對(duì)比
未實(shí)踐里氏替換原則:

實(shí)踐了里氏替換原則:

如何實(shí)踐
里氏替換原則是對(duì)繼承關(guān)系的一種檢驗(yàn):檢驗(yàn)是否真正符合繼承關(guān)系,以避免繼承的濫用。因此,在使用繼承之前,需要反復(fù)思考和確認(rèn)該繼承關(guān)系是否正確,或者當(dāng)前的繼承體系是否還可以支持后續(xù)的需求變更,如果無(wú)法支持,則需要及時(shí)重構(gòu),采用更好的方式來(lái)設(shè)計(jì)程序。