SOLID 設計原則 - Liskov 替換原則(LSP)

遵循「Liskov 替換原則」建議,物件在應用程式因為功能調整抽換相同型別的子類別物件時,不應對原來的功能與程式運算邏輯造成影響。

討論遵循「開放-封閉原則」的過程中,我們導入了介面設計避免類別功能的演進問題,然而不當的設計很可能導致物件替換的問題,造成應用程式執行錯誤,為了說明這個問題,現在回到其中說明的示範程式碼,建立新的範例進行擴充示範。

假若在設計單位轉換功能中,希望可以提供進一步的彈性,增加不同制度的單位轉換功能,調整介面規格程式碼如下:

public interface IUnitConversion
{
    double Conversions(double d);
    double ConversionsUK(double d);
}

其中新增了 ConversionsUK定義,後續的類別實作這個方法成員,以支援英制單位的轉換作業。

public class OZConversion : IUnitConversion
{
    public double Conversions(double o)
    {
        double c = 0.033814;
        double ml = o / c;
        return ml;
    }
    public double ConversionsUK(double o)
    {
        double c = 0.035195;
        double ml = o / c;
        return ml;
    }
}
public class TemperatureConversion : IUnitConversion
{
    public double Conversions(double c)
    {
        double f = c * 9 / 5 + 32;
        return f;
    }
    public double ConversionsUK(double d)
    {
        throw new NotImplementedException();
    }
}

OZConversion 類別實作了ConversionsUK,其中提供英制盎司的單位換算。

TemperatureConversion是溫度轉換功能,基本上這個類別只提供華氏與攝氏的轉換,因此為了 ConversionsUK 並沒有真正完成實作。
回到控制器,另外建立方法如下:

double UnitConversionUK(IUnitConversion conversion, double d)
{
    double result = conversion.ConversionsUK(d);
    return result;
}

這個方法專門支援英制版本的方法引用,現在測試這個方法,於HomeController中,調整其中的動作方法:

public ActionResult Ozml()
{
    OZConversion conversion = new OZConversion();
    double oz = 10;
    double ml = UnitConversionUK(conversion, oz);
    ViewBag.ml = ml;
    return View();
}
public ActionResult Ftoc()
{
    TemperatureConversion conversion = new TemperatureConversion();
    double c = 100;
    double f = UnitConversionUK(conversion, c);
    ViewBag.f = f;
    return View();
}

這兩組動作方法現在引用 UnitConversionUK 方法進行單位轉換。

完成調整之後,重新建置方案,可以順利完成,於瀏覽器檢視,Ozml動作方法不會有問題,但是 Ftoc 出現以下的結果:


由於ConversionsUK方法並沒有在TemperatureConversion類別裏面完成實作,因此這裏發生了錯誤。

這裏的設計違反了 LSP ,在不同的狀況下,相同介面的子型別物件無法直接抽換,導致了錯誤的結果,而且這種錯誤可以通過編譯,直到程式執行的過程中才可能出現。

我們可以透過「介面隔離原則(ISP)」來處理違反 LSP 設計所造成的問題,下一篇持續作說明。




沒有留言: