S.O.I.L.D 之里氏替換原則

心智極客發表於2019-04-28

說明

里氏替換的簡單理解

子類能夠替代父類

我們來舉一個例子來說明該原則。

首先,新建一個類 A

class A
{
    public function fire() {}
}

BA 的子類,並且重寫了 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()
    }
}

總結一下如何才能不違背里氏替換原則

  1. 子類丟擲的異常必須與父類保持一致
  2. 子類的前置條件(呼叫某個方法需滿足的條件)不得大於父類的前置條件
  3. 子類的後置條件(方法返回時必須達到的要求)不得小於父類的後置條件

相關文章