2009-04-15 2 views
77

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

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

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

ответ

119

Там же два вида обычных статических методов:

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

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

+0

Это была именно та проблема, которую я должен был решить - использование или, скорее, неправильное использование объектов Singleton. – overslacked

+0

Спасибо за этот отличный ответ. Мой вопрос в том, что если синглтоны передаются как параметры статическим методам, делает ли это статический метод небезопасным? –

+0

Термины «чистая функция» и «нечистая функция» - это имена, приведенные в функциональном программировании, к тем, что вы называете «безопасными» и «небезопасными» статическими. – Omnimike

3

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

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

"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, которые не привязаны к поведению объекта и поэтому статичны.

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

+2

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

14

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

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

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

+5

Я слышал, что вы говорите, но как может что-то вроде объекта Math инкапсулировать что-либо, кроме поведения? – JonoW

+8

Он просто сказал подозрительно, не так, и он абсолютно прав. –

+2

@JonoW: Math - это особый случай, когда есть много функций без гражданства.Конечно, если вы выполняете функциональное программирование на Java, у вас будет много функций без состояния. –

1

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

5

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

т.е. изменение:

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

в

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

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

3

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

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

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

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

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

0

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

1

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

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

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

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

9

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


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

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

, которую вы называете, как:

StaticClassVersionOne.doSomeFunkyThing(42); 

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

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

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

+4

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

2

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

4

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

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

1

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

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

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

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

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

1

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

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

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

Смежные вопросы