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

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

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


person ina    schedule 03.04.2014    source источник


Ответы (4)


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

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

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

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

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

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

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

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

person Raja Nadar    schedule 03.04.2014
comment
Что вы подразумеваете под интерфейсным подходом (по сравнению со статическим) для вспомогательных классов? Как статические классы будут мешать модульному тестированию? - person ina; 03.04.2014

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

public static class Math

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

person MarcinJuraszek    schedule 03.04.2014

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

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

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

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

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

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

person svick    schedule 03.04.2014
comment
Отличается ли это в средах, отличных от .NET? - person ina; 03.04.2014
comment
@ina О каких других средах вы говорите? Я не могу вспомнить ни одного другого крупного языка, поддерживающего статические классы (конечно, не C++ или Java). - person svick; 03.04.2014

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

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

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

person Vinay Pandey    schedule 03.04.2014