六大設(shè)計(jì)原則之里氏替換原則(Liskov Substitution Principle)

定義

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

我們可以看到,RectangeSquare類都以自己的方式實(shí)現(xiàn)了父類Quadrangle的公共方法。而且由于Square的特殊性,它也聲明了自己獨(dú)有的成員變量_sideLength以及其對(duì)應(yīng)的公共方法。

注意,這里RectangeSquare并不是重寫(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ì)程序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容