Является ли использование многих статических методов плохим?

Я склонен объявлять статическими все методы в классе, когда этот класс не требует отслеживания внутренних состояний. Например, если мне нужно преобразовать A в B и не полагаться на какое-то внутреннее состояние C, которое может меняться, я создаю статическое преобразование. Если есть внутреннее состояние C, которое я хочу настроить, тогда я добавляю конструктор для установки C и не использую статическое преобразование.

Я прочитал различные рекомендации (в том числе и на StackOverflow) НЕ для чрезмерного использования статических методов, но я все еще не понимаю, что это неправильно, с приведенным выше правилом.

Это разумный подход или нет?

Ответ 1

Существуют два типа распространенных статических методов:

  • "Безопасный" статический метод всегда будет давать одинаковый вывод для одних и тех же входных данных. Он не модифицирует глобальные переменные и не вызывает никаких "опасных" статических методов любого класса. По сути, вы используете ограниченное функциональное программирование - не бойтесь их, они в порядке.
  • "Небезопасный" статический метод мутирует глобальное состояние или прокси-серверы к глобальному объекту или какое-то другое не проверяемое поведение. Это возврат к процедурному программированию и должен быть реорганизован, если это вообще возможно.

Существует несколько распространенных видов использования "небезопасной" статики - например, в шаблоне Singleton, но имейте в виду, что, несмотря на любые красивые имена, которые вы им называете, вы просто изменяете глобальные переменные. Подумайте внимательно, прежде чем использовать небезопасную статику.

Ответ 2

Объект без какого-либо внутреннего состояния является подозрительной вещью.

Обычно объекты инкапсулируют состояние и поведение. Объект, который инкапсулирует только поведение, является нечетным. Иногда это пример Легкий или Flyweight.

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

Ответ 3

Это действительно только продолжение замечательного ответа Джона Милликина.


Хотя может быть безопасным сделать статичные методы stateless (которые являются довольно значительными функциями) статическими, иногда это может привести к сочетанию, которое трудно изменить. У вас есть статический метод как таковой:

public class StaticClassVersionOne {
    public static void doSomeFunkyThing(int arg);
}

Которое вы называете:

StaticClassVersionOne.doSomeFunkyThing(42);

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

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

Это может быть или не может быть вероятной ситуацией, но я думаю, что это стоит рассмотреть.

Ответ 4

Другой вариант - добавить их как нестатические методы для исходного объекта:

i.e., изменяя:

public class BarUtil {
    public static Foo transform(Bar toFoo) { ... }
}

в

public class Bar {
    ...
    public Foo transform() { ...}
}

однако во многих ситуациях это невозможно (например, генерация кода обычного кода из XSD/WSDL/etc), или это сделает класс очень длинным, и методы преобразования часто могут быть реальной болью для сложных объектов, и вы просто хочу, чтобы они были в своем отдельном классе. Так что да, у меня есть статические методы в классах полезности.

Ответ 5

Статические классы прекрасны, если они используются в правильных местах.

А именно: методы, которые являются "листовыми" методами (они не изменяют состояние, они просто каким-то образом преобразуют вход). Хорошими примерами этого являются такие вещи, как Path.Combine. Такие вещи полезны и делают синтаксис терминов.

проблемы У меня со статикой множество:

Во-первых, если у вас есть статические классы, зависимости скрыты. Рассмотрим следующее:

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. Теперь увеличьте этот кошмар зависимости, и вы, вероятно, догадаетесь, что произойдет. Представьте, что вы пытаетесь поддерживать код, где нет явного порядка инициализации. Сравните это с вложением зависимостей с экземплярами - в этом случае код даже не будет компилировать, если зависимости не выполняются!

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

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

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

Вот хорошая статья о проблемах: http://gamearchitect.net/2008/09/13/an-anatomy-of-despair-managers-and-contexts/

Ответ 6

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

В вашем случае, когда вы просто преобразуете 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()
}

Изменить: одним из хороших примеров использования статических методов являются html-помощники в Asp.Net MVC или Ruby. Они создают элементы html, которые не привязаны к поведению объекта и поэтому статичны.

Редактировать 2: Изменено функциональное программирование на структурированное программирование (по какой-то причине я запутался), реквизит Торстен для указания этого.

Ответ 7

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

Тем не менее, если вы не используете внутренние данные, они могут использоваться и выполняться немного быстрее. Убедитесь, что вы не трогаете глобальные данные.

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

Ответ 8

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

Ответ 9

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

Ответ 10

Недавно я реорганизовал приложение для удаления/изменения некоторых классов, которые изначально были реализованы как статические классы. Со временем эти классы приобрели так много, и люди просто пометили новые функции как статические, так как никогда не было экземпляра, плавающего вокруг.

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

Ответ 11

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

В любом случае, singleton дает вам возможность позже передать что-то в factory, чтобы получить другой экземпляр и который изменяет поведение всей вашей программы без рефакторинга. Изменение глобального класса статических методов на что-то с разными "резервными" данными или несколько другим поведением (дочерним классом) является основной болью в прикладе.

И статические методы не имеют аналогичного преимущества.

Так что да, они плохие.

Ответ 12

Если это полезный метод, приятно сделать его статическим. Гуава и Apache Commons основываются на этом принципе.

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

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

static cutNotNull(String s, int length){
  return s == null ? null : s.substring(0, length);
}

Одним из преимуществ является то, что я не тестирую такие методы: -)

Ответ 13

Ну, конечно, нет серебряной пули. Статические классы подходят для небольших утилит/помощников. Но использование статических методов для программирования бизнес-логики, безусловно, является злым. Рассмотрим следующий код

   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 (большинство инструментов тестирования не издеваются над статическими классами), и это делает модульное тестирование невозможным. Нет Тесты единиц == Низкое качество

Ответ 14

Если вы знаете, что вам никогда не понадобится использовать внутреннее состояние C, это нормально. Однако, если это когда-либо изменится в будущем, вам нужно будет сделать метод нестационарным. Если это не статично для начала, вы можете просто игнорировать внутреннее состояние, если оно вам не понадобится.

Ответ 15

Статические методы обычно являются плохим выбором даже для кода без атак. Вместо этого создайте одноэлементный класс с этими методами, который создается один раз и вводится в классы, желающие использовать методы. Такие классы легче издеваться и тестироваться. Они гораздо ориентированы на объекты. При необходимости их можно связать с прокси-сервером. Статика делает OO намного сложнее, и я не вижу причин использовать их почти во всех случаях. Не 100%, а почти все.