在設計元件的時候,有時候會遇到元件擴充或是退回版本等…問題,這個時候如果把原件的彈性變大,將會大大的增加工程效率,之前有大概介紹什麼是Liskov原則,這裡將會進一步舉例介紹。

定義

Liskov替換原則的定義請看如下:

1
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.

很好,相信你看過之後,還是不知道他到底想表達什麼,讓我們白話一點詮釋他。

換句話說

在設計物件的時候,你必須考量到彼此的共通性才會進一步的設計彼此的繼承關係。
程式中的物件應該是可以在不改變程式正確性的前提下被它的子類所替換的。
簡單來說就是:當子類別替換父類別時,功能不會受到影響。
換句話也可以說是:子類別必須能取代它的父類別。
繼承的特性使得類別具備高耦合的特性,子類別如果需要Overriding, Overloading 必須遵照父類別的規則,才能符合類別的設計原則。因為具備高耦合的關係,如果沒有特別注意的話,很容易導致程式出現錯誤。

範例

以下設計一個咖啡的範例:

1
2
3
4
5
6
7
class Coffee {
let color = 'black';
let limit = 150;
getFormula = () => {
return 123 * 456;
}
}

再來設計另一種咖啡:

1
2
3
4
class Latte extends Coffee {
let color = 'white';
let limit = 150;
}

這裡設計一個杯子可以把我們的拿鐵裝起來:

1
2
3
4
5
6
class Cup {
let latte;
Cup(Latte latte) {
this.latte = latte;
}
}

我們在使用的時候可以這樣用,取得咖啡和拿鐵的秘方公式:

1
2
Coffee myCoffee = new Coffee();
Coffee myLatte = new Latte();

再來我們可以用杯子把拿鐵裝起來:

1
Cup cup = new Cup(myLatte);

你發現問題了嗎?杯子可以裝拿鐵,卻無法裝咖啡耶?
因此這違反了Liskov替換原則,要修正問題其實也很簡單。
只要讓杯子可以裝basis即可:

1
2
3
4
5
6
class Cup {
let coffee;
Cup(Coffee coffee) {
this.coffee = coffee;
}
}

整理

這裡可以整理一些結論:

  1. 父類別出現的地方,子類別照理來說可以取代他
  2. 子類別要實作父類別的方法
  3. overloading父類別的方法時,參數或回傳結果需要與父類別定義的幾乎一樣。