天天看點

通過示例揭示 C# 中的單一職責原則 – C# SOLID 原則

作者:啟辰8
通過示例揭示 C# 中的單一職責原則 – C# SOLID 原則

單一職責原則(Single-Responsibility Principle),SOLID原則中第一個原則及字母S的含義。

程式設計界大名鼎鼎的SOLID設計原則,哪個開發者不知道? 如果沒有鮑勃叔叔(羅伯特·C·馬丁)教我們,我們該怎麼辦? 如果沒有它們,面向對象程式設計的世界會怎樣?

如果您不熟悉 SOLID 原則,它們在 Robert C. Martin 的“靈活軟體開發原則、模式和實踐”一書中進行了定義。 這些 SOLID 原則适用于一般程式設計,并不專注于任何特定語言。 後來,這本書的C#版又重新出版了,叫做《C# 中的靈活軟體開發原則、模式和實踐》,順便說一句,這是推薦的最好的C#書籍之一。

ob 大叔建立的 SOLID 原則有 5 條,分别是:

  • 單一職責原則 (SRP)
  • 開閉原則 (OSP)
  • 裡氏替換原則 (LSP)
  • 接口隔離原則(ISP)
  • 依賴倒置原則(DIP)

正如您可能已經在标題中看到的那樣,在本文中,我們将讨論和解釋 C# 單一職責原則 (SRP),這是 SOLID 原則中的第一個。

如果您希望了解 C# 中的單一職責原則,或者您有以下問題:

  • C#中的單一職責原則是什麼?
  • 為什麼單一職責原則很重要?
  • 為什麼要使用單一職責原則?
  • 如何在c#中實作單一職責原則?

本文就是你的解藥! 在這裡,您将通過真實示例發現您需要了解的有關 SOLID 原則的所有資訊。

C#中的單一職責原則是什麼?

C#中的單一職責原則是SOLID的第一個設計原則。 單一職責原則也被稱為 SRP ,是以如果你在某個地方看到 SRP,你應該會知道它是什麼。

如果您想知道 SRP 在 C# 中的狀态,單一職責原則定義為:

“每個軟體子產品或類應該隻有一個改變的理由”

顯然,正如原則本身的名稱所表明的那樣,一個類或方法應該隻有一個責任和一個改變的原因。 單一職責原則是 C# SOLID 原則,更容易解釋,但同時也是最難付諸實踐的。

盡管這個改變的單一原因聽起來有點奇怪(在我看來是火星人),但我解釋是:

“強制一個類隻做一件事”

如果您在 C# 中有一個類既做 X 事又做 Y 事,我會懷疑您沒有遵守單一職責原則。 就像開發人員,不管是你還是我,如果我們是開發人員,我們的責任是開發,同時我們做營銷,是的,我們是神,但這并不遵守單一職責原則 !

為什麼要遵循單一職責原則

通過在代碼中實施 SRP,您可以在代碼級别獲得多種好處,還可以節省時間。 遵循 C# SRP 的最大好處是擁有幹淨的 C# 代碼,降低其複雜性并将其變成簡單易讀的代碼。

在 C# 代碼中實作 SRP 的另一個巨大優勢是獲得重用或回收代碼摘錄的能力。 如果遵循單一職責原則,就可以在應用程式的其他部分重用該代碼的邏輯,或者直接在另一個項目中重用它。 您可以節省大量的開發時間!

使用單一職責原則的另一個好處是連貫性,即雖然每個方法做的事情不同,但是當它們聯合在一起時,它們一起做一件事并且把它做好。

我們還将看到并解釋在我們的 C# 代碼中使用 SRP 的主要好處和優勢。

C# 單一職責原則的主要優點

這時你可能想知道在 C# 中使用單一職責原則有什麼好處。 正如我之前所說,使用 SOLID 原則的好處多多。 從對開發和時間的影響來看,以下原因是最重要的原因:

提高代碼的簡潔性:如果遵守且未違反 C# 中的單一職責原則,則類應僅包含單個屬性或方法。 這使得更容易了解啥東西做了什麼,代碼的複雜性也會降低。

維護一個可管理的項目:與之前的優勢相關。 當一個類隻有一項職責時,您的開發者大腦将隻需要考慮一項職責,而無需考慮其他職責。 這使您擁有一個“更幹淨”的思路,并允許您更好地思考和管理項目或應用程式。

增強維護性和可伸縮性:如果我們考慮一下違反 SRP 的類,我們會考慮一個做很多事情的類。 如果出于任何原因必須修改該類,它很可能會傳回以前沒有發生的錯誤。 如果這個類隻做一件事,你隻需要注意修改那件事并檢查它是否正常工作。 您不必擔心更改一件事會破壞另一件事。

提高可測試性:作為任何優秀的 C# 開發人員,我們總是被建議編寫和運作單元測試,并被認為這是個好做法。 通過遵循 SRP,我們會發現在類中編寫單元測試是一項容易的任務,因為類隻做一件事而不是幾件事,是以減少了錯誤修複并減少了測試時間。

減少耦合:如果方法隻做一件事,那麼我們 C# 開發人員将避免的事情之一就是耦合。 也就是說,一個方法不會依賴于另一個方法,這使得它們彼此更加獨立。

促進代碼擴充:如果在 C# 中滿足單一職責原則,我們就有可能将這些類組合起來,建構更加使用者友好和子產品化的代碼。 這可以通過依賴注入獲得更可測試和可擴充的代碼。

如何在 C# 中實作單一職責原則

隻要您知道每個類的職責,很容易遵循 C# 中的單一職責原則。 這就是能夠在我們的應用程式或開發中實施 SRP 的訣竅:了解每個類的職責。

讓我們看幾個自我解釋的示例,以便您了解如何在違反 SRP 原則的 C# 代碼中實施該原則。

沒有單一職責原則實施的 C# 示例

對于軟體 C# 代碼摘錄的第一個案例研究示例,我們看到了如何違反 SRP,特别是 Add 方法。

add方法一方面是添加使用者,另一方面是寫入日志。 對于一個方法來說,這些事情太多了,它應該隻知道并處理一件事或責任。

using System;
using System.IO;

namespace CsharpSingleResponsibilityPrinciple
{
    class Srp : IPrinciple
    {
        public string Principle()
        {
            return "Single Responsibility Principle in C#";
        }
    }

    internal class User
    {
         public void Add(Database db)
        {
            try
            {
                db.Add();
            }
            catch (Exception ex)
            {
                File.WriteAllText(@"C:\something\Error.txt", ex.ToString());
            }
        }
    }
}           

例如,我們應該一方面将添加使用者的操作分開,另一方面将寫入日志的操作分開。

在 C# 中實作單一職責原則

讓我們看看下面的例子。 在這個例子中,我們按照 SRP 原則重構了代碼,将職責分離到幾個類中。

using System;
using System.IO;

namespace CsharpSingleResponsibilityPrinciple
{
    class UserSRP
    {
        private FileLogger logger = new FileLogger();
        public void Add(Database db)
        {
            try {
                db.Add();
            }
            catch (Exception ex)
            {
                logger.Handle(ex.ToString());
            }
        }
    }
    internal class Logger
    {
        public void Handle(string error)
        {
            File.WriteAllText(@"C:\something\Error.txt", error);
        }
    }
}           

如您所見,Logger 被抽象了。 在一方面,我們有類 UserSRP 來添加使用者和類 Logger 來将錯誤寫入日志。

通過這種簡單的方式,我們尊重 SRP 原則,我們可以以更簡單的方式在其他地方重用代碼。 但是,即使 SRP 得到實施并且沒有被違反,也可以用更好的方式來完成。

在 C# 中更好地實作 SRP

正如我之前所說,SRP 正确的在代碼中實作了,但總有改進的餘地。 那麼現在讓我們看看如何更好地實作它。

using System;
using System.IO;

namespace CsharpSingleResponsibilityPrinciple
{
    class Wrapper
    {
        public void HandleAdd(FileLogger logger, Database db, User user)
        {
            try
            {
                user.Add(db);
            }
            catch (Exception error)
            {
                logger.Handle(error.ToString());
            }
        }
    }
}           

在此示例中,我們可以看到 Add 方法已包裝在錯誤處理程式中。 這樣使用者隻知道如何添加使用者,這是他唯一的責任。

真的沒有必要走到這一步,這是一種非常準時的方式,可能并不适用于所有情況。 隻要您的每個類隻處理一項職責,您就符合 SRP 原則。

實踐中的單一職責原則示例

我們在開發一個項目的時候,一定要考慮到SRP,遵守它,尤其要給我們的代碼可重用性。 讓我們看一下 C# 中的單一職責原則示例,看看我們如何減少耦合并增加内聚性。

違反 C# 中的單一職責原則(真實世界的例子)

假設我們有一個使用者日志記錄。 在 C# 中的第一個 SRP 示例中,我們看到 UserSettings 類具有多項職責,例如擷取使用者和驗證其憑據。 顯然這可行,但我們違反了 SRP。

class UserSettings
{
    private User User;

    public UserSettings(User user)
    {
        User = user;
    }

    public void UpdateSettings(Settings settings)
    {
        if (ConfirmSMS())
        {
            // ...
        }
    }

    private bool ConfirmSMS()
    {
        // ...
    }
}           

為了解決 C# 中違反單一職責原則的問題,我們應該将此類的方法分離到幾個不同的類中。

在 C# 中重構 SRP

正如我之前所說,最好的解決方案是将功能拆分為 2 個類,這樣每個類中隻有一個任務。 在此示例中,類 UserAuth 誕生了(用于識别和驗證使用者),保留類 UserSettings,它負責其餘的使用者設定。

class UserAuth
{
    private User User;

    public UserAuth(User user)
    {
        User = user;
    }

    public bool ConfirmSMS()
    {
        // ...
    }
}

class UserSettings
{
    private User User;
    private UserAuth Auth;

    public UserSettings(User user)
    {
        User = user;
        Auth = new UserAuth(user);
    }

    public void UpdateSettings(Settings settings)
    {
        if (Auth.ConfirmSMS())
        {
            // ...
        }
    }
}           

現在是的,現在每個類隻負責一個職責。 例如,如果在某個時候我們需要更改職責,那麼就像修改單個類一樣簡單。

檢測是否違反了單一職責原則

在 C# 中違反單一職責原則的情況可能以不同的方式發生,并且确實不可能 100% 确定有沒有違反此 SOLID 原則。

在這種情況下,理想的做法是了解以下提示,以檢測是否違反了 C# 中的單一職責原則,并将您所了解的有關該原則的所有知識付諸實踐:

導入的數量:始終關注導入數量,因為如果這個數字很高,很可能我們正在導入的類數量非常多,那麼,也許我們正在做額外的事情。

公共方法(public methods)的數量:在 C# 中檢測 SRP 違規的一種簡單方法是檢查一個類有多少公共方法。 如果你想知道為什麼,如果一個類有大量不同的公共方法,那麼很可能該類同時做幾件事并導緻不遵守單一職責原則。

測試很困難:一個好的做法是設法在類上編寫單元測試。 如果編寫這些單元測試需要很高成本,那麼很可能該類同時在做幾件事。

行數:雖然這看起來很荒謬,但這通常是識别違反單一職責原則的方法。 您隻需檢視類的大小及其行數。 如果您懷疑它的功能代碼過多,可能不僅是一項任務,并且還承擔着不止一項責任。

這些是檢測是确定是否不滿足此原則的最常用方法。 确實沒有具體或更好或更壞的提示,這取決于許多因素,上面提到的這四個技巧在大多數情況下通常可以檢測 SRP 違規。

如果一個類有多個職責會怎樣?

的确,有時候遵守單一職責原則和 SOLID 原則并不容易或不可能。 我這樣說是因為在某些情況下,一個類可能承擔不止一項責任。 我想到的第一個例子是:一個類,它一方面從伺服器讀取資訊,另一方面寫入或上傳資訊。

幾乎可以肯定的是,就像我經常說的那樣,該類在做了太多事情,如果在某個時候需要進行一些更改,你要多做好準備!

在這些情況下,兩個衆所周知的術語用于描述類或子產品同時做幾件事的原因。 這兩個術語是内聚和耦合。 讓我們了解它們:

低内聚

内聚是指各種資料或元素彼此相關的強度。 如果幾個類高度依賴其他類,這意味着内聚水準很低,顯然違反了單一職責原則。

一個低内聚的例子對代碼來說是不健康的,如果我們需要修改一個類,另一個類可能依賴于其他類。 這可能會導緻代碼中出現不必要的錯誤。

高耦合

耦合是程式設計中使用的術語,指的是系統各個子產品之間的依賴或獨立程度(類似于内聚)。 如果你有一個高耦合,它會類似于内聚,子產品在它們之間有一個進階别的耦合,并且再次違反了單一職責原則。

例如,如果您想更改子產品的某些函數并需要對其他地方進行修改,則具有高耦合性,這些更改很可能會由于依賴關系而影響其他子產品并且導緻某些東西會中斷。

你可能想知道,是否可以在 C# 中解決内聚和耦合問題? 如何以簡單的方式修複它?

讓我提前告訴你是的,而且是用一種非常簡單的方式。

修複 C# 中的内聚和耦合

這個看似無解的問題,答案很簡單。 就像将那個類的職責拆分成幾個不同的簡單類!

最佳實踐之一是使用接口方法矩陣 (IMUM) 來了解哪些方法應該在另一個類中,或者哪些方法應該分組。

此時您可能想知道:IMUM 是什麼意思? 它如何幫助避免内聚和違反 SRP? 讓我們來看看。

接口方法使用矩陣 (IMUM) 解釋

您很可能不熟悉 IMUM 及其對接口方法使用矩陣的定義。 該術語由克羅地亞 .NET 開發人員 Kristijan Kralj 建立。

要正式定義術語 IMUM,正如 Krisijan 解釋的那樣,它是由兩個軸構成的矩陣:X 軸和 Y 軸。 一個軸上是方法,另一個軸上是接口。

在下面的示例中,您可以在 X 軸上看到我們擁有的接口,在 Y 軸上看到方法。 通過這種方式,您可以輕松地将具有相同職責的方法分組,并檢測是否有任何違反單一職責原則的情況。

您可能已經注意到,通過這個簡單的 IMUM 矩陣,您可以清楚地看到哪個接口正在使用哪些方法。

就在這個例子中,為了避免違反單一職責原則,我們可以注意到 MethodC 和 MethodD 具有高耦合度和低内聚度。 正如 Krisijan 所說,這可以表明他們負有相同的責任。

繼續閱讀