Использование статических методов - это плохо? - PullRequest
88 голосов
/ 15 апреля 2009

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

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

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

Ответы [ 15 ]

141 голосов
/ 15 апреля 2009

Существует два вида общих статических методов:

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

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

15 голосов
/ 15 апреля 2009

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

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

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

11 голосов
/ 15 апреля 2009

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


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

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

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

StaticClassVersionOne.doSomeFunkyThing(42);

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

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

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

6 голосов
/ 15 апреля 2009

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

т. Е. Меняется:

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

в

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

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

4 голосов
/ 15 апреля 2009

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

А именно: Методы, которые являются «листовыми» методами (они не изменяют состояние, они просто каким-то образом преобразуют входные данные). Хорошими примерами этого являются такие вещи, как 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/

4 голосов
/ 15 апреля 2009

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

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

  • В некоторых языках также есть переменные уровня класса, которые допускают инкапсуляцию данных и статические методы.
4 голосов
/ 15 апреля 2009

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

В вашем случае, когда вы просто преобразовываете 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: функциональное программирование изменено на структурированное программирование (по какой-то причине я запутался), опрашивает Торстена за указание на это.

3 голосов
/ 15 апреля 2009

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

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

2 голосов
/ 15 апреля 2009

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

2 голосов
/ 15 апреля 2009

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

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

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

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

...