Liskov substitution principle
里氏替換原則(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 | public class Vehicle |
1 | public class Navigator |
但若是將火車(Train)作為車輛(Vehicle)的子類則會違反里氏替換原則,這是因為火車沒有右轉(TurnRight)與左轉(TurnLeft)的行為,因此火車不能作為車輛(Vehicle)的子類。
為了修正,可以將轉彎(Turn)和移動(Move)動作抽象為介面可轉彎(ITurnable)和可移動(IMovable)
1 | public interface ITurnable |
並將車輛分為可以在一般道路上行駛的車輛RoadVehicle,它實作可轉彎(ITurnable)和可移動(IMovable)
1 | public class RoadVehicle : IMovable, ITurnable |
與需要在軌道上行駛的車輛RailVehicle,它只實作可移動(IMovable)
1 | public class RailVehicle : IMovable |
最後,定義汽車和火車的類:
1 | public class Car : RoadVehicle |
這樣,Navigator 類可以安全地操作所有可以移動的交通工具,而不需要關心它們是否能轉彎:
1 | public class Navigator |