說明
里氏替換的簡單理解
子類能夠替代父類
我們來舉一個例子來說明該原則。
首先,新建一個類 A
class A
{
public function fire() {}
}
類 B
為 A
的子類,並且重寫了 fire
方法
class B extends A
{
public function fire() {}
}
根據里氏替換原則,子類必須能夠替代父類。也就是說,雖然子類重寫了父類的方法,但是在能夠使用父類的場景裡面,也一定要能夠使用子類。例如,我們的定義一個 doSomething
方法,傳入的是類 A
function doSomething(A $obj)
{
// do something with it.
}
根據里氏替換原則,doSomething
方法同樣也應該適用於類 B
反面示例
父類
class VideoPlayer
{
public function play($file)
{
// 播放視訊
}
}
子類
use Exception;
class AviVideoPlayer extends VideoPlayer
{
public function play($file)
{
if (pathinfo($file, PATHINFO_EXTENSION) != 'avi')
{
throw new Exception;
}
}
}
子類重寫了 play
方法,且丟擲了異常,而父類並沒有丟擲異常。很明顯,子類並不能完全替代父類,因此,該例子違反了里氏原則。
面向介面程式設計
如何才能不違背里氏原則呢?正確的做法就是「面向介面程式設計」。
首先,用介面定義好方法
interface LessonRepositoryInterface
{
/**
* Fetch all records.
*
* @return array
*/
public function getAll();
}
介面作出了約定,子類的實現介面就必須遵從這些約定,一定程度上保證其能遵守里氏原則。
class FileLessonRepository implements LessonRepositoryInterface
{
public function getAll()
{
return [];
}
}
class DbLessonRepository implements LessonRepositoryInterface
{
public function getAll()
{
return Lesson::all()->toArray()
}
}
總結一下如何才能不違背里氏替換原則
- 子類丟擲的異常必須與父類保持一致
- 子類的前置條件(呼叫某個方法需滿足的條件)不得大於父類的前置條件
- 子類的後置條件(方法返回時必須達到的要求)不得小於父類的後置條件