Плохо ли использовать много статических методов?
Я склонен объявлять статическими все методы в классе, когда этот класс не требует отслеживания внутренних состояний. Например, если мне нужно преобразовать A в B и не полагаться на какое-то внутреннее состояние C, которое может изменяться, я создаю статическое преобразование. Если есть внутреннее состояние C, которое я хочу настроить, я добавляю конструктор для установки C и не использую статическое преобразование.
Я читал различные рекомендации (в том числе по StackOverflow), чтобы не злоупотреблять статикой но я все еще не понимаю, что не так с эмпирическим правилом выше.
Это разумный подход или нет?
15 ответов
существует два вида общих статических методов:
- "безопасный" статический метод всегда будет давать один и тот же выход для одних и тех же входов. Он не изменяет глобалы и не вызывает никаких "небезопасных" статических методов любого класса. По сути, вы используете ограниченный вид функционального программирования - не бойтесь их, они в порядке.
- "небезопасный" статический метод мутирует глобальное состояние, или прокси к глобальному объекту, или какое-либо другое не проверяемое поведение. Это пережиток процедурного программирования и должны быть переработаны, если это вообще возможно.
есть несколько распространенных применений "небезопасной" статики - например, в Одноэлементном шаблоне-но имейте в виду, что, несмотря на любые красивые имена, которые вы называете их, вы просто мутируете глобальные переменные. Подумайте хорошенько перед использованием небезопасной статики.
объект без какого-либо внутреннего состояния-это подозрительная вещь.
обычно объекты инкапсулируют состояние и поведение. Объект, который только инкапсулирует поведение, является нечетным. Иногда это пример легкий или Flyweight.
в других случаях это процедурный дизайн, выполненный на языке объектов.
это действительно только продолжение Великого ответа Джона Милликина.
хотя можно безопасно сделать методы без состояния (которые в значительной степени являются функциями) статическими, иногда это может привести к соединению, которое трудно изменить. Рассмотрим, что у вас есть статический метод как таковой:
public class StaticClassVersionOne {
public static void doSomeFunkyThing(int arg);
}
что вы называете:
StaticClassVersionOne.doSomeFunkyThing(42);
что все хорошо и хорошо, и очень удобно, пока вы не столкнетесь с случаем, когда вам нужно изменить поведение статического способ, и обнаружите, что вы тесно связаны с StaticClassVersionOne
. Возможно, вы могли бы изменить код, и это было бы хорошо, но если бы были другие вызывающие абоненты, зависящие от старого поведения, их нужно будет учитывать в теле метода. В некоторых случаях этот метод может стать довольно уродливым или недостижимым, если он попытается сбалансировать все эти поведения. Если вы разделите методы, вам может потребоваться изменить код в нескольких местах, чтобы учесть его, или сделать вызовы new занятия.
но подумайте, если вы создали интерфейс для предоставления метода и дали его вызывающим, теперь, когда поведение должно измениться, можно создать новый класс для реализации интерфейса, который является более чистым, более легко тестируемым и более доступным для обслуживания, и который вместо этого предоставляется вызывающим. В этом случае вызывающие классы не нужно изменять или даже перекомпилировать, и изменения локализованы.
это может быть или не может быть вероятной ситуацией, но я думаю, это стоит рассмотреть.
другой вариант-добавить их в качестве нестатических методов в исходный объект:
т. е., меняется:
public class BarUtil {
public static Foo transform(Bar toFoo) { ... }
}
на
public class Bar {
...
public Foo transform() { ...}
}
статические классы в порядке, если они используются в нужных местах.
а именно: методы, которые являются "листовыми" методами (они не изменяют состояние, они просто каким-то образом преобразуют вход). Хорошими примерами этого являются такие вещи, как пути.Объединять. Такие вещи полезны и делают для синтаксиса terser.
на проблемы у меня со статикой многочисленны:
во-первых, если у вас есть статические классы, зависимостей скрыты. Рассмотрим следующий:
public static class ResourceLoader
{
public static void Init(string _rootPath) { ... etc. }
public static void GetResource(string _resourceName) { ... etc. }
public static void Quit() { ... etc. }
}
public static class TextureManager
{
private static Dictionary<string, Texture> m_textures;
public static Init(IEnumerable<GraphicsFormat> _formats)
{
m_textures = new Dictionary<string, Texture>();
foreach(var graphicsFormat in _formats)
{
// do something to create loading classes for all
// supported formats or some other contrived example!
}
}
public static Texture GetTexture(string _path)
{
if(m_textures.ContainsKey(_path))
return m_textures[_path];
// How do we know that ResourceLoader is valid at this point?
var texture = ResourceLoader.LoadResource(_path);
m_textures.Add(_path, texture);
return texture;
}
public static Quit() { ... cleanup code }
}
глядя на TextureManager, вы не можете сказать, какие шаги инициализации должны выполняться, глядя на конструктор. Вы должны проникнуть в класс, чтобы найти его зависимости и инициализировать вещи в правильном порядке. В этом случае перед запуском необходимо инициализировать ResourceLoader. Теперь масштабируйте этот кошмар зависимости, и вы, вероятно, можете догадаться, что произойдет. Представьте, что вы пытаетесь поддерживать код там, где нет явного порядка инициализация. Сравните это с инъекцией зависимостей с экземплярами - в этом случае код даже не будет compile если зависимости не выполняются!
кроме того, если вы используете статику, которая изменяет состояние, это похоже на карточный домик. Вы никогда не знаете, у кого есть доступ к чему, и дизайн имеет тенденцию напоминать монстра спагетти.
наконец, и что не менее важно, использование статики связывает программу с конкретной реализацией. Статический код антитезис проектирования для тестируемости. Тестирование кода, пронизанного статикой, - это кошмар. Статический вызов никогда не может быть заменен на тестовый двойной (если вы не используете тестовые фреймворки, специально предназначенные для макетирования статических типов), поэтому статическая система заставляет все, что использует ее, быть мгновенным интеграционным тестом.
короче говоря, статика хороша для некоторых вещей, а для небольших инструментов или одноразового кода я бы не препятствовал их использованию. Однако, помимо этого, они являются кровавыми кошмар для ремонтопригодности, хорошего дизайна и простоты тестирования.
вот хорошая статья о проблемах: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/
причина, по которой вас предостерегают от статических методов, заключается в том, что их использование теряет одно из преимуществ объектов. Объекты предназначены для инкапсуляции данных. Это предотвращает непредвиденные побочные эффекты от случаться который избегает ошибок. Статические методы не имеют инкапсулированных данных* и поэтому не получают этого преимущества.
тем не менее, если вы не используете внутренние данные, они хорошо использовать и немного быстрее выполнять. Убедитесь, что вы не касаетесь глобальных данных в них хотя.
- некоторые языки также имеют переменные уровня класса, которые позволят инкапсулировать данные и статические методы.
это кажется разумным подходом. Почему вы не хотите использовать слишком много статических классов/методов, которые вы в конечном итоге отходит от объектно-ориентированного программирования и в области структурного программирования.
в вашем случае, когда вы просто преобразуете A В B, скажите, что все, что мы делаем, это преобразуем текст, чтобы перейти от
"hello" =>(transform)=> "<b>Hello!</b>"
тогда статический метод имел бы смысл.
однако, если вы вызываете эти статические методы объект часто, и он имеет тенденцию быть уникальным для многих вызовов (например, способ его использования зависит от ввода), или это часть неотъемлемого поведения объекта, было бы разумно сделать его частью объекта и поддерживать его состояние. Один из способов сделать это-реализовать его как интерфейс.
class Interface{
method toHtml(){
return transformed string (e.g. "<b>Hello!</b>")
}
method toConsole(){
return transformed string (e.g. "printf Hello!")
}
}
class Object implements Interface {
mystring = "hello"
//the implementations of the interface would yield the necessary
//functionality, and it is reusable across the board since it
//is an interface so... you can make it specific to the object
method toHtml()
method toConsole()
}
Edit: одним из хороших примеров большого использования статических методов являются вспомогательные методы html в Asp.Net MVC или Ruby. Они создают html-элементы, которые не привязаны к поведению объект, и, следовательно, статический.
Edit 2: изменено функциональное программирование на структурированное Программирование (по какой-то причине я запутался), реквизит Торстену для указания на это.
недавно я переработал приложение для удаления / изменения некоторых классов, которые были первоначально реализованы как статические классы. Со временем эти классы приобрели так много, и люди просто продолжали помечать новые функции как статические, так как никогда не было экземпляра, плавающего вокруг.
Итак, мой ответ заключается в том, что статические классы не обязательно плохо, но это может быть проще начать создавать экземпляры сейчас, то есть рефакторинг позже.
Я бы счел это дизайнерским запахом. Если вы используете в основном статические методы, у вас, вероятно, не очень хороший дизайн OO. Это не обязательно плохо, но, как и все запахи, это заставит меня остановиться и переоценить. Это намекает на то, что вы можете сделать лучший дизайн OO или, возможно, вам следует пойти в другом направлении и полностью избегать OO для этой проблемы.
пока не внутреннее состояние приходит в игру, это нормально. Обратите внимание, что обычно статические методы должны быть потокобезопасными, поэтому при использовании вспомогательных структур данных используйте их потокобезопасным образом.
Я обычно ходил туда и обратно между классом с кучей статических методов и синглтоном. Оба решают проблему, но синглтон может быть гораздо легче заменен более чем одним. (Программисты всегда кажутся настолько уверенными, что будет только 1 чего-то, и я обнаружил, что ошибался достаточно раз, чтобы полностью отказаться от статических методов, за исключением некоторых очень ограниченных случаев).
в любом случае, синглтон дает вам возможность позже пройти ТО на завод, чтобы получить другой экземпляр и это изменяет поведение всей вашей программы без рефакторинга. Изменение глобального класса статических методов во что-то с другими "поддерживающими" данными или немного другим поведением (дочерний класс) - большая боль в заднице.
и статические методы не имеют подобных преимуществ.
Так что да, они плохие.
Если это метод утилиты, приятно сделать его статическим. Guava и Apache Commons построены на этом принципе.
мое мнение об этом чисто прагматично. Если это ваш код приложения, статические методы, как правило,не самое лучшее. Статические методы имеют серьезные ограничения модульного тестирования - их нелегко высмеять: вы не можете внедрить высмеиваемую статическую функциональность в какой-либо другой тест. Вы также не можете обычно вводить функциональность в статический метод.
Так в моей логике приложения у меня обычно есть небольшие статические вызовы методов, подобные утилите. Т. е.
static cutNotNull(String s, int length){
return s == null ? null : s.substring(0, length);
}
одним из преимуществ является то, что я не испытываю таких методов :-)
ну, серебряной пули, конечно, нет. Статические классы подходят для небольших утилит / помощников. Но использование статических методов для программирования бизнес-логики, безусловно, зло. Рассмотрим следующий код
public class BusinessService
{
public Guid CreateItem(Item newItem, Guid userID, Guid ownerID)
{
var newItemId = itemsRepository.Create(createItem, userID, ownerID);
**var searchItem = ItemsProcessor.SplitItem(newItem);**
searchRepository.Add(searchItem);
return newItemId;
}
}
вы видите вызов статического метода ItemsProcessor.SplitItem(newItem);
пахнет делу
- у вас нет явной объявленной зависимости, и если вы не копаетесь в коде, вы можете упустить связь между вашим классом и контейнером статического метода
- вы не можете тест
BusinessService
изолируя его отItemsProcessor
(большинство тестовых инструментов не издеваются над статическими классами), и это делает модульное тестирование невозможным. Нет модульных тестов = = низкое качество
Если вы знаете, вы будете никогда нужно использовать внутреннее состояние C, все в порядке. Если это когда-либо изменится в будущем, вам нужно будет сделать метод нестатическим. Если он нестатичен для начала, вы можете просто игнорировать внутреннее состояние, если оно вам не нужно.
статические методы, как правило, являются плохим выбором даже для кода без состояния. Вместо этого создайте одноэлементный класс с этими методами, который создается один раз и вводится в те классы, которые хотят использовать методы. Такие занятия легче поддразнивать и тестировать. Они гораздо более объектно-ориентированным. При необходимости вы можете обернуть их прокси-сервером. Статика делает OO намного сложнее, и я не вижу причин использовать их почти во всех случаях. Не 100%, но почти все.