里氏替換原則

Derived classes must be substitutable for their base classes.
Robert C. Martin

里氏替換原則,全名 Liskov substitution principle,簡稱 LSP

定義

子類別要能替換掉父類別而不影響程式架構,並且所有父類別能做的事,子類別也要可以做

秘訣

  • 對介面寫程式,重點放在要執行的動作
  • 方法簽名、回傳值與丟出的異常要一致
  • 思考繼承時子類別所造成的「行為變化」要如何設計才不會違反父類別介面的合約

提醒

  • 依賴子類別的地方不能用父類別取代
  • 拋棄繼承,思考可否改用組合的方式

範例

來看看範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Scooter
{
private $name;
private $fuelTankCapacity;
public function getName(){};
public function getFuelTankCapacity(){};
}

class Yamaha extends Scooter()
{
...
}

class Kymco extends Scooter()
{
...
}

class Gogoro extends Scooter()
{
...
}

是不是有點怪怪的…Gogoro 沒有油箱啊!!!
子類別並沒有完全實作父類別有的功能,違反了里氏替換原則。
那麼來稍微改一下程式。

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
class ElectricScooter
{
private $name;
private $batteryCapacity;
public function getName(){};
public function getBatteryCapacity(){};
}

class Gogoro extends ElectricScooter()
{
...
}

class Scooterist
{
private $scooter;
public function setScooter(Scooter $scooter)
{
$this->scooter = $scooter;
}
}

$yamaha = new Yamaha();
$scooterist = new Scooterist();
$Scooterist->setScooter($yamaha);

另外定義一個 ElectricScooter 類別,然後讓 Gogoro 去繼承它,就解決了這個問題。
並且每個子類別都可以替代其父類別,也符合里氏替換原則。最後,也許再把動力來源抽出來定義成介面會更好,不過還是要實際情況來考慮。

Reference

PHP 也有 Day #19 - PHP 返樸歸真系列之從實例學設計模式 by 大澤木小鐵 (Jace Ju)​
物件導向設計原則 SOLID
SOLID:五則皆變
The Principles of OOD