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)

本文我們來介紹單一功能原則

單一功能原則

在面向對象編程領域中,單一功能原則(Single responsibility principle)規定每個類都應該有且僅有一個單一的功能,並且該功能應該由這個類完全封裝起來。所有它的(這個類的)服務都應該嚴密的和該功能平行(功能平行,意味着沒有依賴)。

這個術語由羅伯特·C·馬丁(Robert Cecil Martin)在他的《敏捷軟件開發,原則,模式和實踐》一書中的一篇名為『面向對象設計原則』的文章中提出。馬丁表述該原則是基於《結構化分析和系統規格》一書中的內聚原則(Cohesion)之上的。

馬丁把功能(職責)定義為:「改變的原因」,並總結出一個類或者模塊應該有且只有一個改變的原因。一個具體的例子就是,想像有一個用於編輯和打印報表的模塊。這樣的一個模塊存在兩個改變的原因。第一,報表的內容可以改變(編輯)。第二,報表的格式可以改變(打印)。這兩方面的改變會因為完全不同的起因而發生:一個是本質的修改,一個是表面的修改。單一功能原則認為這兩方面的問題事實上是兩個分離的功能,因此他們應該分離在不同的類或者模塊里。把具有不同的改變原因的事物耦合在一起的設計是糟糕的。

保持一個類專註於單一功能點的一個重要的原因是,它可以使類更加的健壯。回顧上面的例子,如果有一個對於報表「編輯」流程的修改,那麼將存在極大的危險性,因為假設這兩個功能存在於同一個類中,修改報表的「編輯」流程會導致公共狀態或者依賴關係的改變,從而可能使「打印」功能的代碼無法正常運行。

C# 示例

例如,考慮這樣一個應用程序,它接受一組形狀(圓形和正方形),並計算該列表中所有形狀的面積之和。

首先,創建形狀類,並通過構造函數設置所需的參數。

對於正方形,需要知道它的邊長:

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

對於圓形,需要它的半徑:

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

    public double Radius { get; init; }
}

接下來,創建 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();
    }

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

要使用 AreaCalculator 類,您需要實例化這個類,並傳入一個形狀列表,並顯示其輸出。

在此,我們傳入一個三個形狀的列表:一個半徑為 2 的圓,一個邊長為 5 的正方形,一個邊長為 6 的正方形。

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

    var areas = new AreaCalculator(shapes);
    Console.WriteLine(areas.Output());
}

運行程序,您會看到如下的輸出:

Sum of the areas of provided shapes: 73.56637061435917

輸出正常,但這並不符合單一功能原則。因為 AreaCalculator 類既計算了所有形狀的面積之和,又處理了輸出數據的格式。

考慮這樣一個場景,假如想要輸出轉換為另一種格式呢,如 JSON。我們就需要去修改 AreaCalculator 類,這樣本來是為了修改輸出數據的格式,卻可能會影響到計算的邏輯,這明顯違反了單一功能原則

正確的示範

AreaCalculator 類應該只關心計算提供的形狀的面積之和,不應該關心輸出什麼格式。

下面我們來做一些修改,刪除 AreaCalculator 類中的 Output 方法:

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();
    }
}

並新增一個 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<object> {
            new Circle(2),
            new Square(5),
            new Square(6)
    };

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

運行程序,輸出結果如下:

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

現在,AreaCalculator 類處理計算邏輯,SumCalculatorOutputter 類處理輸出格式,它們各司其職,遵循了單一功能原則

總結

本文我介紹了 SOLID 原則中的單一功能原則(single-responsibility principle),並通過 C# 代碼示例簡明地詮釋了它的含意和實現,希望對您有所幫助。

作者 : 技術譯民
出品 : 技術譯站

參考文檔: