Liskov substitution Principle
如果類別 T 有一個子類別 S,那麼 T 可以被 S 替換而不會發生任何錯誤。
if S is a subtype of T, then objects of type T may be replaced with objects of type S .
衍生的子類別必須可以替換成他們繼承的父類別。
Derived classes must be substitutable for their base classes .
Example
- Bad way:
class Rectangle {
constructor(private width: number, private length: number) {}
public setWidth(width: number) {
this.width = width;
}
public setLength(length: number) {
this.length = length;
}
public getArea() {
return this.width * this.length;
}
}
class Square extends Rectangle {
constructor(side: number) {
super(side, side);
}
public setWidth(width: number) {
// A square must maintain equal sides
super.setWidth(width);
super.setLength(width);
}
public setLength(length: number) {
super.setWidth(length);
super.setLength(length);
}
}
const rect: Rectangle = new Square(10); // Can be either a Rectangle or a Square
rect.setWidth(20);
expect(rect.getArea()).toBe(200); // ❌ If rect is a Square, area is 400
Ooops
const rect: Rectangle = new Square(10);
rect.setWidth(20);
if (rect instanceof Square) {
// ...
} else {
// ...
}
- Good way:
Type checking a polymorphic value is a good indicator of an LSP violation.
interface Shape {
getArea: () => number;
}
interface Rectangle extends Shape {
width: number;
length: number;
}
interface Square extends Shape {
sides: number;
}
// Implementation...
Example 2
- LoadingProvider Component 的 children 參數,符合 LSP
function LoadingProvider(isLoading: boolean, children: React.ReactNode) {
return isLoading ? <svg>...</svg> : children;
}
function Button({ text: string }) {
return <button>{text}</button>;
}
function Image({ url: string }) {
return <img href={url} />;
}
<LoadingProvider isLoading={isLoading}>
<Button text='Click me!' />
</LoadingProvider>
<LoadingProvider isLoading={isLoading}>
<Image url="https://sample.img.url" />
</LoadingProvider>