里氏替換原則(Liskov substitution principle
):子類(Subclass
)必須要可以替代父類(Super class
),即任何父類出現的地方,都可以使用其子類替換,而不影響程式的正確性。
在考慮里氏替換原則時,可以遵循以下幾點:
避免移除特性
:如果在父類的行為中,如果子類中沒有這個特性的話,你可能違反了里氏替換原則(If you are removing features when subclassing, you are likely breaking Liskov substitution
)。在設計時,
如果子類中出現了NotImplementedException
,那你可能違反了里氏替換原則
如果出現只能是空白的方法也可能違反了里氏替換原則
保持抽象簡單
:盡量讓抽象保持簡單(Keep abstractions simple
):如果在基類放入越多的邏輯,那你有很大的機率違反了里氏替換原則。基類應只包含子類可以共有的方法。
子類需要有與基類相同的公有成員(A subclass needs to have the same public members as the base class
):這些成員在呼叫時還需要具有相同的行為。
先考慮類的API再建立層次結構
:在建立類的層次結構前先考慮它們的API(Consider the class API before establishing class hierarchies
):現實中的分類並不總能完全轉為類的結構,例如汽車(Car)與火車(Train)皆是車輛但它們不能直接繼承同一個父類,最好是將它們分開繼承
優先考慮組合而非繼承
(Favor composition over inheritance
):在實作時,先考慮使用介面 (Interface
) 或是將行為委託給其他類,而不是直接使用繼承。
例如:假設有一個交通工具 (Vehicle) 的類層次結構,會思考:汽車 (Car) 和卡車 (Truck) 繼承自交通工具 (Vehicle),
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Vehicle { public float Speed { get ; set ; } public Vector3 Direction { get ; set ; } public virtual void GoForward () { } public virtual void Reverse () { } public virtual void TurnRight () { } public virtual void TurnLeft () { } }
1 2 3 4 5 6 7 8 9 10 11 public class Navigator { public void Move (Vehicle vehicle ) { vehicle.GoForward(); vehicle.TurnLeft(); vehicle.GoForward(); vehicle.TurnRight(); vehicle.GoForward(); } }
但若是將火車(Train
)作為車輛(Vehicle
)的子類則會違反里氏替換原則
,這是因為火車沒有右轉(TurnRight
)與左轉(TurnLeft
)的行為,因此火車不能作為車輛(Vehicle
)的子類。
為了修正,可以將轉彎(Turn
)和移動(Move
)動作抽象為介面可轉彎(ITurnable
)和可移動(IMovable
)
1 2 3 4 5 6 7 8 9 10 11 public interface ITurnable { void TurnRight () ; void TurnLeft () ; } public interface IMovable { void GoForward () ; void Reverse () ; }
並將車輛分為可以在一般道路上行駛的車輛RoadVehicle
,它實作可轉彎(ITurnable
)和可移動(IMovable
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class RoadVehicle : IMovable , ITurnable { public float Speed { get ; set ; } public float TurnSpeed { get ; set ; } public virtual void GoForward () { } public virtual void Reverse () { } public virtual void TurnLeft () { } public virtual void TurnRight () { } }
與需要在軌道上行駛的車輛RailVehicle
,它只實作可移動(IMovable
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class RailVehicle : IMovable { public float Speed { get ; set ; } public virtual void GoForward () { } public virtual void Reverse () { } }
最後,定義汽車和火車的類:
1 2 3 4 5 6 7 8 9 public class Car : RoadVehicle { } public class Train : RailVehicle { }
這樣,Navigator 類可以安全地操作所有可以移動的交通工具,而不需要關心它們是否能轉彎:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Navigator { public void Move (IMovable movable ) { movable.GoForward(); movable.Reverse(); } public void Turn (ITurnable turnable ) { turnable.TurnLeft(); turnable.TurnRight(); } }