何时将整个类声明为静态

本文关键字:声明 静态 何时 | 更新日期: 2023-09-27 17:56:13

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

另外,我不确定这个准则是什么意思:"不要将静态类视为杂项桶。 - 我有几个类只是一堆杂项静态函数......

何时将整个类声明为静态

像这样的类static是完全可以的,事实上,如果你看System.Math你会发现它也static

public static class Math

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

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

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

似乎这正是您的情况。

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

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

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

帮助程序类通常是静态类,因此不需要实例化它们。实例化托管 .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;
 }
}

现在,如果你去尝试对此方法进行单元测试,公共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);
}

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

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

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

话虽如此,如果你们的方法都不依赖于实例变量,你可以让你的类成为静态的。

静态类有几个好处,还有更多。