天天看点

通过示例揭示 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 所说,这可以表明他们负有相同的责任。

继续阅读