裡氏替換原則
定義:
所有引用基類的地方必須能透明地使用其子類的對象。
通俗點講,只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。
但是這裡我們需要注意的是:有子類出現的地方,父類未必就能適應。
優點:
代碼共用,減少建立類的工作量,每個子類都擁有父類的方法和屬性;
提高代碼的重用性;
子類可以形似父類,但又異於父類;
提高代碼的可擴充性;
提高產品或項目的開放性。
缺點:
繼承是侵入性的,只要繼承就必須擁有父類的所有屬性和方法;
降低代碼的靈活性,子類必須擁有父類的屬性和方法,讓子類增加了約束;
增強了耦合性,當父類的常量、變數和方法被修改時,必須考慮子類的修改。
含義:
1、子類必須完全實現父類的方法
例:
如,我們知道槍有很多種,而且有一個共同的功能就是射擊。Soldier中我們添加了一個KillEnemy()方法,這個方法使用槍來殺敵人,具體使用哪個槍取決於選用哪個 槍。
代碼:
public abstract class AbstractGun { public abstract void Shoot(); }
class Handgun : AbstractGun { public override void Shoot() { Console.WriteLine("手槍射擊"); } }
class Rifle:AbstractGun { public override void Shoot() { Console.WriteLine("步槍射擊"); } }
class MachineGun : AbstractGun { public override void Shoot() { Console.WriteLine("機槍射擊"); } }
public class Soldier { private AbstractGun gun; public AbstractGun Gun { set { this.gun = value; } get { return gun; } } public void KillEnemy() { Console.WriteLine("士兵開始射擊"); gun.Shoot(); } }
用戶端:
static void Main(string[] args) { Soldier soldier = new Soldier(); soldier.Gun = new Handgun(); soldier.KillEnemy(); }
注意:在類中調用其他類時務必要用父類或介面,若不能使用,則說明類的設計已經違背LSP原則。
但是現在有一把玩具槍,但是我們知道玩具槍是不能像其它的槍一樣射擊並擊殺敵人的,所以玩具槍不能真正實現Shoot方法。
class ToyGun : AbstractGun { public override void Shoot() { //玩具槍不能射擊殺人,所以不能真正實現此方法 } }
在這種情況下,我們發現業務調用類已經出現問題了,正常的商務邏輯不能運行。這裡有兩種處理方法:
(1)在Soldier類中增加判斷,如果是模擬槍,就不用來殺人。這個方法可以解決問題,但是在程式中,我們每增加一個類,所有與這個父類有關係的類都必須修改,這樣就不可行了。所以這個方案被否定了。
(2)ToyGun脫離繼承,建立一個獨立的父類,可以與AbstractGun建立關聯委託關係。類圖如下:
通常應用中經常發生如上情況,按LSP原則:若子類不能完整地實現父類的方法,或者父類的某些方法在子類中發生畸變,則建議斷開父子繼承關係,採用依賴、聚集、組合等關係代替繼承。
2、子類可以有自己的個性
因為有這個規則,裡氏替換原則不能反過來使用,在子類出現的地方,父類不一定可以勝任。
3、覆蓋或者實現父類的方法的時候輸入的參數可以被放大
子類的輸入參數類型的範圍要大於等於父類的參數。當子類代替父類傳遞到調用者中,父類執行,子類永遠不會被執行,這是正確的。如果你想讓子類執行,必須覆寫父類的方法。但若反過來,子類參數類型的範圍小於父類,那麼父類存在的地方,子類就未必可以存在 。則子類就會被執行,這會影發商務邏輯混亂,因為在實際應用中父類一般是抽像類,子類是實作類別,這樣的一個子類會歪曲父類的意圖。
所以子類中方法方法的前置條件必須與父類中被覆寫的方法的前置條件相同或者更寬鬆。
4.覆蓋或實現父類的方法時輸出結果可以被縮小
比如,父類的一個方法傳回值是類型T,子類的相同方法(重載或覆蓋)的傳回值是S,那麼裡氏替換原則就要求S必須小於等於T,也就是說,要麼S和T是同一個類型,要麼S是T的子類。
總結:
採用裡氏替換原則的目的就是增強程式的健壯性,版本升級時也可以保持非常好的相容性。即使增加子類,原有的子類也可以繼續運行。在實際項目中,每個子類對應不同的業務含義,使用父類作為參數,傳遞不同的子類完成不同的商務邏輯。