Liskov Substitution Principle (LSP)

 

定義:

衍生類別(Sub Type)必須能夠替換成他們的基礎類別(Base Type)。
衍生類別應該可以替換任何基楚類別出現的位置,且程序還能正常工作。
 

說明:

1. 不能僅用 is-a 的關係就建立繼承,必須考慮是否在基礎類別中有些方法對衍生類別而言是不需要或是無意義的。
這些沒有意義的方法會造成不可預期的結果。
2.一些違反 LSP 觀察點(基本上從衍生類別刪除基礎類別的功能就代表了無法取代基礎類別,因而違反了LSP)
1.退化函式(不一定絕對違反LSP,但需要檢查一下),基本上退化函式就是繼承基礎類別的函式,但在該函式中沒有做任何事,這種函式就是沒有意義的。

public class BaseClass {
  public void method(){
    //do something
  }
}
public class DeriveClass extends BaseClass{
  @Override
  public void method() {
    //do nothing
  }
}

DeriveClass 的 method 可能違反了LSP
2.在衍生類別丟出基礎類別沒有的異常

注意:

必須要有繼承關係才需要考慮 LSP ,而 LSP 是讓設計達到 OCP 的規則之一 。
 

Example Code :

Vehicle 有2個簡單的方法, 分別是 drive (駕駛)和 openWindow (打開車窗)。

public class Vehicle {
    public void drive(){
        System.out.println("drive vehicle");
    }
    public void openWindow(){
        System.out.println("open window");
    }
}

若單以語意而言 Vehicle 和 Bike  有 is-a 的關係,也有共同的方法 drive , 但 openWindow 對 Bike 而言是沒有意義的。
若強制讓 Bike 繼承 Vehicle 會有2種情況。
1. Bike 不覆寫 openWindow 方法

public class Bike extends Vehicle{
    public void drive(){
        System.out.println("drive Bike");
    }
}

2. Bike 覆寫 openWindow 方法,但方法內容令人迷惑。

public class Bike extends Vehicle{
    public void drive(){
        System.out.println("drive Bike");
    }
    public void openWindow(){
        System.out.println("Bike can't open window");
    }
}

不論哪種方法都無法到達呼叫端預期的結果 e.g.

Vehicle bike = new Bike();
bike.openWindow();// why bike can open window?

因此 Bike 繼承 Vehicle 不符合 LSP 。
 
如何讓 Bike 符合 LSP?
我們可以重新建立抽象層 Vehicle 並提取 Bike 和 Car 共同的部份並放入 Vehicle 中,並讓2者都派生 Vehicle。

public interface Vehicle {
    public void drive();
}

Bike 實作 Vehicle

public class Bike implements Vehicle{
    public void drive(){
        System.out.println("drive Bike");
    }
}

Car 實作 Vehicle

public class Car implements Vehicle{
    public void drive(){
        System.out.println("drive car");
    }
    public void openWindow(){
        System.out.println("open window");
    }
}

最後以語意來說 Car , Bike 都是 Vehicle。 Vehicle 的方法對於 Car , Bike 都是有意義的。