Когда объявлять весь класс как статический

У меня есть математический вспомогательный класс, где каждая функция является статической, то есть, параметры передаются в качестве аргументов, возвращаемое значение. Должен ли я объявить весь класс как статический? Повлияет ли добавление статического модификатора в класс на производительность?

Кроме того, я не уверен, что означает это руководство: "не относитесь к статическим классам как к разным группам". - У меня есть несколько классов, которые являются просто кучей разных статических функций...

4 ответа

Решение

Вспомогательные классы обычно являются статическими классами, поэтому вам не нужно создавать их экземпляры. Создание экземпляра управляемого объекта.NET (особенно вспомогательных классов) не требует больших затрат, это просто вопрос удобства.

Исключительно заманчиво просто собрать статический класс с минимальными вспомогательными методами и выполнить свою работу. Они занимают свое место в коде и могут использоваться, особенно когда есть детерминированный ввод / вывод. например, ComputeHash строки, Find Average из чисел и т. д.

Но единственная причина, по которой статические классы не приветствуются, заключается в том, что они обычно мешают юнит-тестированию и создают всевозможные проблемы. (Подделки, родинки, частные средства доступа и т. Д.)

Интерфейсный подход даже для вспомогательных классов помогает в модульном тестировании всего кода. Это особенно актуально для больших проектов, в которых задействованы рабочие процессы, так что статические вспомогательные методы являются лишь частью рабочего процесса.

Например, предположим, вам нужно проверить, является ли текущий год високосным. Заманчиво написать быстрый статический метод.

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

Как вы можете видеть, в тот момент, когда вспомогательные методы были основаны на интерфейсе, они легко тестировались. В ходе большого проекта многие такие меньшие статические методы в конечном итоге приводят к узким местам в тестировании ключевых функциональных потоков.

Так что стоит заранее знать об этой ловушке и делать дополнительные инвестиции заранее. Определите, какие классы / методы должны быть статическими, а какие - нет.

Совершенно нормально делать такие занятия static на самом деле, если вы посмотрите на System.Math вы увидите это static также:

public static class Math

Руководство пытается сказать, что вы не должны помещать все имеющиеся у вас статические методы в один статический класс, который бы все делал и играл роль корзины для статических методов. Вместо этого, если это уместно, создайте меньшие классы утилит с методами, относящимися к той же функциональности, как это делается с System.Math и еще пара в BCL.

Должен ли я объявить весь класс как статический?

Да. Добавление static класс говорит, что он содержит только статические члены и что вы никогда не сможете его создать. Без этого пользователи вашего класса могут запутаться и попытаться создать экземпляр или переменную вашего класса. С static, это невозможно.

Кажется, это как раз ваш случай.

Повлияет ли добавление статического модификатора в класс на производительность?

Нет, вызов статического метода всегда будет иметь одинаковые характеристики производительности, не имеет значения, является ли содержащий класс static или нет. На самом деле, целой концепции статических классов не существует на уровне CIL, это просто запечатанные абстрактные классы (комбинация, которая не будет компилироваться в C#).

Но даже если бы была разница, она была бы крошечной. Не оптимизируйте преждевременно, особенно когда речь идет о микрооптимизации.

Все начинается с того, когда у меня должен быть статический метод, и именно тогда у вас нет никакой зависимости от переменных экземпляра.

Теперь, если сказано, что ни один из ваших методов не зависит от переменной экземпляра, вы можете сделать свой класс статическим.

Статический класс служит нескольким преимуществам и многим другим.

Другие вопросы по тегам