定義
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基類的地方必須透明的使用其子類的對象。
定義明確的說,只要父類能出現(xiàn)的地方子類也可以出現(xiàn),而且替換為子類不會產(chǎn)生任何錯誤或異常,但是反過來就不行,有子類出現(xiàn)的地方,父類未必就能適應(yīng)。
繼承
優(yōu)點
代碼共享,減少創(chuàng)建類的工作量,每個子類都擁有父類的方法和屬性
提高代碼的重用性
子類可以形似父類,但是又異于父類。
提高代碼的可擴(kuò)展性,實現(xiàn)父類的方法就可以了。許多開源框架的擴(kuò)展接口都是通過繼承父類來完成。
提高產(chǎn)品或項目的開放性
缺點
繼承是侵入性的,只要繼承,就必須擁有父類的所有方法和屬性
降低了代碼的靈活性,子類必須擁有父類的屬性和方法,讓子類有了一些約束
增加了耦合性,當(dāng)父類的常量,變量和方法被修改了,需要考慮子類的修改,這種修改可能帶來非常糟糕的結(jié)果,要重構(gòu)大量的代碼。
從整體來看,利大于弊。怎么才能讓利的因素發(fā)揮最大的作用,同時減少弊的影響?解決方案是引入里氏替換原則。
里氏替換原則的規(guī)范
1.子類必須完全實現(xiàn)父類的方法
我們在做系統(tǒng)設(shè)計時,經(jīng)常會定義一個接口或抽象類,然后編碼實現(xiàn),調(diào)用類則直接傳入接口或抽象類,其實這已經(jīng)使用了里氏替換原則。
注意:?
如果子類不能完整地實現(xiàn)父類的方法,或者父類的一些方法在子類中已經(jīng)發(fā)生畸變,則建議斷開繼承關(guān)系,采用依賴,聚集,組合等關(guān)系代替繼承。
2.子類可以有自己的個性
子類當(dāng)然可以有自己的行為和外觀,也就是方法和屬性。但是里氏替換原則可以正著用,但是不能反著用。在子類出現(xiàn)的地方,父類未必就可以勝任。
下面的兩條不適合ios,ios 沒辦法區(qū)分參數(shù)類型是放大還是縮小,不檢測參數(shù)類型。
3.覆蓋或?qū)崿F(xiàn)父類的方法時輸入?yún)?shù)可以被放大
4.覆蓋或?qū)崿F(xiàn)父類的方法時輸出結(jié)果可以被縮小
場景模擬
我們以士兵射擊為例。士兵設(shè)計可以使用很多槍步槍,手槍等等
士兵射擊UML圖

簡單代碼
@protocol Gun<NSObject>
-(void)shoot;
@end
#import "Gun.h"
@interface Solider :
?NSObject-(void)setGun:(id<Gun>) gun;
-(void)killEnemy;
@end
#import "Solider.h"
@interface Solider()
@property (nonatomic,strong) idsoliderGun;
@end
@implementation Solider
-(void)setGun:(id<Gun>)gun{
? ? self.soliderGun = gun;
}
-(void)killEnemy{
? ? [self.soliderGun shoot];
}
@end
#import "Gun.h"
@interface HandGun : NSObject<Gun>
@end
#import "HandGun.h"
@implementation HandGun
-(void)shoot{
? ? NSLog(@"手槍射擊");
}
@end
#import "Gun.h"
@interface Rifle : NSObject<Gun>
@end
#import "Rifle.h"
@implementation Rifle
-(void)shoot{
? ? NSLog(@"步槍射擊");
}
@end
#import "Gun.h"
@interface MachineGun : NSObject<Gun>
@end
#import "MachineGun.h"
@implementation MachineGun
-(void)shoot{
? ? NSLog(@"機(jī)關(guān)槍射擊");
}
@end
測試代碼
Solider * solider=[[Solider alloc]init];
id<Gun> gun=[HandGun new];
? [solider setGun:gun];
[solider killEnemy];
gun=[Rifle new];
[solider setGun:gun];
[solider killEnemy];
gun=[MachineGun new];
[solider setGun:gun];
[solider killEnemy];
結(jié)果
2018-04-03 15:50:37.308121+0800 設(shè)計模式原則[54809:5684654] 手槍射擊
2018-04-03 15:50:37.308331+0800 設(shè)計模式原則[54809:5684654] 步槍射擊
2018-04-03 15:50:37.308615+0800 設(shè)計模式原則[54809:5684654] 機(jī)關(guān)槍射擊
上面這個實現(xiàn)就是也就是開閉原則。
場景模擬變更
我們用步槍射擊的時候,其實步槍有很多種,有AK,AUG狙擊槍。但是當(dāng)我們用狙擊步槍的時候我們需要打開瞄準(zhǔn)鏡才能射擊。
場景變更UML 圖

場景變更代碼
#import "Rifle.h"
@interface AUG : Rifle
-(void)zoomOut;
@end
#import "AUG.h"
@implementation AUG
-(void)zoomOut{
? ? NSLog(@"打開放大鏡");
}
-(void)shoot{
? ? NSLog(@"使用狙擊槍射擊");
}
@end
solider 增加
-(void)killEnemy:(AUG*)aug;
-(void)killEnemy:(AUG*)aug{
? ? [aug zoomOut];
? ? [aug shoot];
}
測試代碼
AUG * aug = [AUG new];
? ? [solider killEnemy:aug];
結(jié)果
2018-04-03 16:13:57.310796+0800 設(shè)計模式原則[60744:5711673] 打開放大鏡
2018-04-03 16:13:57.311321+0800 設(shè)計模式原則[60744:5711673] 使用狙擊槍射擊
當(dāng)我們給-(void)killEnemy:(AUG*)aug函數(shù)傳入一個?MachineGun 對象的時候回發(fā)生崩潰
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MachineGun zoomOut]: unrecognized selector sent to instance 0x60000000a6a0'
從這里可以看出里氏替換原則的缺點了。不可以向下兼容
這證明了里氏替換原則可以正著用,但是不能反著用。在子類出現(xiàn)的地方,父類未必就可以勝任。
里氏替換原則經(jīng)驗
在項目中,采用里氏替換原則時,盡量避免子類的“個性”,一旦子類有了“個性”,這個子類和父類之間的關(guān)系就難調(diào)和,把子類當(dāng)做父類使用,子類的“個性”被抺殺了,把子類單獨作為一個業(yè)務(wù)來使用,則會讓代碼間的耦合關(guān)系變得撲朔迷離–缺乏類替換的標(biāo)準(zhǔn)。(別人的經(jīng)驗)
參考博客
下一篇博客