C#面向對象編程中開閉原則的示例詳解

在面向對象編程中,SOLID 是五個設計原則的首字母縮寫,旨在使軟件設計更易於理解、靈活和可維護。這些原則是由美國軟件工程師和講師羅伯特·C·馬丁(Robert Cecil Martin)提出的許多原則的子集,在他2000年的論文《設計原則與設計模式》中首次提出。

SOLID 原則包含:

  • S:單一功能原則(single-responsibility principle)
  • O:開閉原則(open-closed principle)
  • L:裡氏替換原則(Liskov substitution principle)
  • I:接口隔離原則(Interface segregation principle)
  • D:依賴反轉原則(Dependency inversion principle)

本文我們來介紹開閉原則。

開閉原則

在面向對象編程領域中,開閉原則 (open-closed principle, OCP) 規定軟件中的對象(類,模塊,函數等等)應該對於擴展是開放的,而對於修改是封閉的”,這意味著一個實體是允許在不改變它的源代碼的前提下變更它的行為。該特性在產品化的環境中是特別有價值的,在這種環境中,改變源代碼需要代碼審查,單元測試以及諸如此類的用以確保產品使用品質的過程。遵循開閉原則的代碼在擴展時並不發生改變,因此無需這些過程。

具體到類,也就是說,在不修改類本身代碼的情況下,應該是可以擴展它的行為的。

C# 示例

讓我們回顧一下上一篇文章單一功能原則中提到的 AreaCalculator 類,

class AreaCalculator
{
    private List<object> _shapes;

    public AreaCalculator(List<object> shapes)
    {
        _shapes = shapes;
    }

    /// <summary>
    /// 計算所有形狀的面積總和
    /// </summary>
    /// <returns></returns>
    public double Sum()
    {
        List<double> areas = new List<double>();

        foreach (var item in _shapes)
        {
            if (item is Square s)
            {
                areas.Add(Math.Pow(s.SideLength, 2));
            }
            else if (item is Circle c)
            {
                areas.Add(Math.PI * Math.Pow(c.Radius, 2));
            }
        }

        return areas.Sum();
    }
}

對於上面的計算方法,考慮這樣一種場景,用戶想要計算一些其它形狀的面積總和,比如三角形、矩形、五邊形等等…… 您將不得不反復編輯此類以添加更多的 if/else 塊,這就違反瞭開閉原則。

改進

一個更好的做法是,將計算每個形狀的面積的邏輯從 AreaCalculator 類中移除,並將其添加到對應每個形狀的類中。我們可以定義一個帶有 CalcArea 方法的接口 IShape,然後讓每個形狀都實現這個接口。

接口 IShape:

interface IShape
{
    /// <summary>
    /// 計算面積
    /// </summary>
    /// <returns></returns>
    double CalcArea();
}

修改後的 Square 和 Circle 類:

/// <summary>
/// 正方形
/// </summary>
class Square : IShape
{
    public Square(double length)
    {
        SideLength = length;
    }
    public double SideLength { get; init; }

    public double CalcArea()
    {
        return Math.Pow(SideLength, 2);
    }
}

/// <summary>
/// 圓形
/// </summary>
class Circle : IShape
{
    public Circle(double radius)
    {
        Radius = radius;
    }

    public double Radius { get; init; }

    public double CalcArea()
    {
        return Math.PI * Math.Pow(Radius, 2);
    }
}

AreaCalculator 類也要對應做一些修改:

class AreaCalculator
{
    private List<IShape> _shapes;

    public AreaCalculator(List<IShape> shapes)
    {
        _shapes = shapes;
    }

    /// <summary>
    /// 計算面積總和
    /// </summary>
    /// <returns></returns>
    public double Sum()
    {
        List<double> areas = new List<double>();

        foreach (var item in _shapes)
        {
            areas.Add(item.CalcArea());
        }

        return areas.Sum();
    }
}

此時,如果我們有一個新的形狀需要進行計算,我們可以直接添加一個實現瞭接口 IShape 的新類,而無需修改 AreaCalculator 類的代碼,比如添加一個長方形類:

/// <summary>
/// 長方形
/// </summary>
class Rectangle : IShape
{
    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public double Width { get; init; }
    public double Height { get; init; }

    public double CalcArea()
    {
        return Width * Height;
    }
}

處理輸出格式的 SumCalculatorOutputter 類同樣無需修改:

class SumCalculatorOutputter
{
    protected AreaCalculator _calculator;

    public SumCalculatorOutputter(AreaCalculator calculator)
    {
        _calculator = calculator;
    }

    public string String()
    {
        return $"Sum of the areas of provided shapes: {_calculator.Sum()}";
    }

    public string JSON()
    {
        var data = new { Sum = _calculator.Sum() };
        return System.Text.Json.JsonSerializer.Serialize(data);
    }
}

然後,我們修改 Main 方法中的代碼來測試一下:

static void Main(string[] args)
{
    var shapes = new List<IShape> {
            new Circle(2),
            new Square(5),
            new Rectangle(2,3)
    };

    var areaCalculator = new AreaCalculator(shapes);
    var outputer = new SumCalculatorOutputter(areaCalculator);
    Console.WriteLine(outputer.JSON());
    Console.WriteLine(outputer.String());
}

運行一下,輸出結果為:

{"Sum":43.56637061435917}
Sum of the areas of provided shapes: 43.56637061435917

現在,這些類的設計,既遵循瞭單一功能原則,又遵循瞭開閉原則。

總結

本文我介紹瞭 SOLID 原則中的開閉原則 (open-closed principle),並通過 C# 代碼示例簡明地詮釋瞭它的含意和實現,希望對您有所幫助。

參考文檔:

https://www.digitalocean.com/community/conceptual_articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design

到此這篇關於C#面向對象編程中開閉原則的示例詳解的文章就介紹到這瞭,更多相關C#開閉原則內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: