【问题标题】:When to declare the entire class as static何时将整个类声明为静态
【发布时间】:2014-05-14 15:10:00
【问题描述】:

我有一个数学助手类,其中每个函数都是静态的,即作为参数输入的参数,返回值。我应该将整个类声明为静态的吗?将静态修饰符添加到类中会对性能产生影响吗?

另外,我不确定this guideline 在“不要将静态类视为杂项桶”中的含义。 - 我有一些只是一堆杂项静态函数的类......

【问题讨论】:

    标签: c# .net oop static-methods static-classes


    【解决方案1】:

    创建像static 这样的类非常好,事实上,如果您查看System.Math,您也会看到它也是static

    public static class Math
    

    该指南试图说的是,您不应该将您拥有的每个静态方法都放在一个静态类中,该类可以做所有事情并扮演静态方法的存储桶的角色。相反,如果合适,可以使用与相同功能相关的方法创建更小的 util 类,就像使用 System.Math 完成的那样,并在 BCL 中结合更多。

    【讨论】:

      【解决方案2】:

      Helper 类通常是静态类,因此您不需要实例化它们。实例化一个托管的 .NET 对象(尤其是帮助类)并没有太大的成本,只是为了方便。

      将静态类与最少的辅助方法组合在一起并完成工作是非常诱人的。它们在代码中占有一席之地,尤其是在存在确定性输入/输出时可以使用。例如计算字符串的哈希,查找数字的平均值等。

      但是,不鼓励使用静态类的一个原因是它们通常会干扰单元测试并带来各种问题。 (假货、痣、私人访问者等)

      甚至是帮助类的基于接口的方法,有助于对整个代码进行单元测试。对于涉及工作流的大型项目尤其如此,静态辅助方法只是工作流的一部分。

      例如假设您需要检查当前年份是否为闰年。编写一个快速的静态方法很诱人。

      public static class DateHelper
      {
       public static bool IsLeapYear()
       {
        var currentDate = DateTime.UtcNow;
        // check if currentDate's year is a leap year using some unicorn logic
        return true; // or false
       }
      }
      

      如果在您的代码中使用此方法,例如:

      public class Birthday
      {
       public int GetLeapYearDaysData()
       {
         // some self-logic..
      
         // now call our static method
         var isLeapYear = DateHelper.IsLeapYear();
      
         // based on this value, you might return 100 or 200.
      
         if (isLeapYear)
         {
          return 100;
         }
      
         return 200;
       }
      }
      

      现在,如果你尝试对这个方法 public int GetLeapYearDaysData() 进行单元测试,你可能会遇到麻烦,因为返回值是不确定的.. 即取决于当前年份,不建议进行单元测试随着时间的推移,行为不可预测/恶化。

      // this unit test is flaky
      [Test]
      public void TestGetLeapYearDaysData()
      {
       var expected = 100;
      
       // we don't know if this method will return 100 or 200.
       var actual = new Birthday().GetLeapYearDaysData();
      
       Assert.AreEqual(expected, actual);
      }
      

      出现上述问题是因为我们无法控制/模拟上述代码中的方法 IsLeapYear()。所以我们任其摆布。

      现在想象以下设计:

      public interface IDateHelper
      {
       bool IsLeapYear();
      }
      
      public class DateHelper : IDateHelper
      {
       public bool IsLeapYear()
       {
        var currentDate = DateTime.UtcNow;
        // check if currentDate's year is a leap year using some unicorn logic
        return true; // or false
       }
      }
      

      现在我们的生日类可以注入一个助手:

      public class Birthday
      {
       private IDateHelper _dateHelper;
      
       // any caller can inject their own version of dateHelper.
       public Birthday(IDateHelper dateHelper)
       {
        this._dateHelper = dateHelper;
       }
      
       public int GetLeapYearDaysData()
       {
         // some self-logic..
      
         // now call our injected helper's method.
         var isLeapYear = this._dateHelper.IsLeapYear();
      
         // based on this value, you might return 100 or 200.
      
         if (isLeapYear)
         {
          return 100;
         }
      
         return 200;
       }
      }
      
      // now see how are unit tests can be more robust and reliable
      
      // this unit test is more robust
      [Test]
      public void TestGetLeapYearDaysData()
      {
       var expected = 100;
      
       // use any mocking framework or stubbed class
       // to reliably tell the unit test that 100 needs to be returned.
      
       var mockDateHelper = new Mock<IDateHelper>();
      
       // make the mock helper return true for leap year check.
       // we're no longer at the mercy of current date time.
      
       mockDateHelper.Setup(m=>m.IsLeapYear()).Returns(true);
      
       // inject this mock DateHelper in our BirthDay class
       // we know for sure the value that'll be returned.
       var actual = new Birthday(mockDateHelper).GetLeapYearDaysData();
      
       Assert.AreEqual(expected, actual);
      }
      

      如您所见,帮助方法基于接口的那一刻,它们很容易测试。在一个大项目的过程中,许多此类较小的静态方法最终会导致测试关键功能流程的瓶颈。

      因此,提前意识到这个陷阱并提前进行额外投资是值得的。基本上确定哪些类/方法需要是静态的,哪些不应该是静态的。

      【讨论】:

      • 帮助类的接口方法(相对于静态方法)是什么意思?静态类如何干扰单元测试?
      【解决方案3】:

      我应该将整个类声明为静态的吗?

      是的。将static 添加到一个类中表示它只包含静态成员并且您永远无法实例化它。没有它,您的类的用户可能会感到困惑并尝试创建您的类的实例或变量。使用static,这是不可能的。

      看来这正是你的情况。

      将静态修饰符添加到类中会影响性能吗?

      不,对静态方法的调用将始终具有相同的性能特征,包含类是否为static 无关紧要。实际上,静态类的整个概念在 CIL 级别并不存在,它们只是密封的抽象类(在 C# 中无法编译的组合)。

      但即使有差异,也将是微乎其微的。不要过早优化,尤其是在微优化方面。

      【讨论】:

      • 这在 .NET 以外的环境中是否有所不同?
      • @ina 你在说什么其他环境?我想不出任何其他支持静态类的主要语言(当然不是 C++ 或 Java)。
      【解决方案4】:

      这一切都始于我什么时候应该有一个静态方法,那就是你对实例变量没有任何依赖。

      现在说如果你的方法都不依赖于实例变量,你可以让你的类静态。

      静态类服务于多个benefits,等等。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-07-17
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        • 1970-01-01
        • 1970-01-01
        • 2018-05-28
        • 2015-07-12
        相关资源
        最近更新 更多