Чи погано використовувати багато статичних методів?


97

Я схильний оголошувати статичними всі методи в класі, коли цей клас не вимагає відстеження внутрішніх станів. Наприклад, якщо мені потрібно перетворити A на B і не покладатися на якийсь внутрішній стан C, який може відрізнятися, я створюю статичне перетворення. Якщо є внутрішній стан C, який я хочу мати можливість налаштувати, то я додаю конструктор для встановлення C і не використовую статичне перетворення.

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

Це розумний підхід чи ні?

Відповіді:


152

Існує два типи поширених статичних методів:

  • "Безпечний" статичний метод завжди дасть однакові результати для тих самих входів. Він не змінює жодних глобальних символів і не викликає жодних "небезпечних" статичних методів будь-якого класу. По суті, ви використовуєте обмежений вид функціонального програмування - не бійтеся цього, вони в порядку.
  • "Небезпечний" статичний метод мутує глобальний стан, або проксі до глобального об'єкта, або якусь іншу поведінку, яку не можна перевірити. Це повернення до процедурного програмування, і їх слід змінити, якщо це можливо.

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


Саме цю проблему мені довелося вирішити - використання, точніше, неправильне використання об’єктів Сінглтон.
оверсайз

Дякую за цю чудову відповідь. Моє запитання полягає в тому, що якщо одиночні кнопки передаються як параметри статичним методам, чи це робить статичний метод небезпечним?
Tony D

1
Терміни "чиста функція" та "нечиста функція" - це імена, що даються у функціональному програмуванні тому, що ви називаєте "безпечною" та "небезпечною" статикою.
Omnimike

19

Об’єкт без будь-якого внутрішнього стану - це підозріла річ.

Зазвичай об'єкти інкапсулюють стан і поведінку. Об’єкт, який лише інкапсулює поведінку, є непарним. Іноді це приклад легкої та легкої ваги .

В інших випадках це процедурний дизайн, виконаний мовою об’єктів.


6
Я чую, що ви говорите, але як щось на зразок об'єкта Math може містити все, крім поведінки?
JonoW

10
Він просто сказав підозріло, а не неправильно, і він абсолютно правий.
Білл К

2
@JonoW: Математика - це дуже особливий випадок, коли існує безліч функцій без громадянства. Звичайно, якщо ви виконуєте функціональне програмування на Java, у вас буде багато функцій без стану.
S.Lott

13

Це насправді лише продовження чудової відповіді Джона Міллікіна.


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

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

Яке ви називаєте як:

StaticClassVersionOne.doSomeFunkyThing(42);

Що все добре і дуже зручно, і дуже зручно, поки ви не натрапите на випадок, коли вам доведеться змінити поведінку статичного методу, і виявити, що ви тісно пов'язані з цим StaticClassVersionOne. Можливо, ви можете змінити код, і це було б добре, але якби були інші абоненти, які залежать від старої поведінки, їх потрібно буде врахувати в тілі методу. У деяких випадках тіло методу може стати досить потворним або неможливим, якщо воно намагається збалансувати всі ці поведінки. Якщо ви розділите методи, можливо, вам доведеться змінити код у кількох місцях, щоб це врахувати, або зробити дзвінки до нових класів.

Але врахуйте, якщо ви створили інтерфейс для надання методу та надали його абонентам, тепер, коли поведінка має змінитися, можна створити новий клас для реалізації інтерфейсу, який є більш чистим, легшим для тестування та більш ремонтом, і це замість цього надається абонентам. У цьому сценарії класи викликів не потребують змін або навіть перекомпіляції, а зміни локалізовані.

Це може бути або не бути ймовірною ситуацією, але я думаю, що це варто задуматися.


5
Я стверджую, що це не лише ймовірний сценарій, це робить статику в крайньому випадку. Статистика також робить TDD кошмаром. Де б ви не використовували статику, ви не можете знущатися, ви повинні знати, що таке вхід і вихід для тестування неспорідненого класу. Тепер, якщо ви зміните поведінку статики, ваші тести на непов'язаних класах, які використовують цю статику, будуть порушені. Крім того, стає прихованою залежністю, яку ви не можете передати конструктору, щоб повідомити розробників про потенційно важливу залежність.
DanCaveman

6

Інший варіант - додати їх як нестатичні методи до вихідного об'єкта:

тобто зміна:

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

в

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

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


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/


4

Причиною того, що вас попереджають від статичних методів, є те, що їх використання втрачає одну з переваг об’єктів. Об'єкти призначені для інкапсуляції даних. Це запобігає виникненню несподіваних побічних ефектів, що дозволяє уникнути помилок. Статичні методи не мають капсульованих даних *, тому не користуйтесь цією перевагою.

Однак, якщо ви не користуєтесь внутрішніми даними, вони прекрасні у використанні та трохи швидші. Переконайтеся, що ви не торкаєтесь глобальних даних у них.

  • Деякі мови також мають змінні рівня класу, які дозволять інкапсуляцію даних та статичні методи.

4

Це видається розумним підходом. Причиною того, що ви не хочете використовувати занадто багато статичних класів / методів, є те, що ви в кінцевому підсумку віддаляєтесь від об'єктно-орієнтованого програмування і більше у сферу структурованого програмування.

У вашому випадку, коли ви просто перетворюєте А на В, скажіть, що все, що ми робимо, це перетворення тексту, з якого потрібно перейти

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


2
Я не думаю, що використання статичних методів кваліфікується як функціональне програмування, тому я здогадуюсь, що ви маєте на увазі структуроване програмування.
Торстен Марек

3

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

Отже, моя відповідь полягає в тому, що статичні класи не є по суті поганими, але може бути простіше почати створювати екземпляри зараз, потім доведеться переробляти пізніше.


3

Я вважаю це дизайнерським запахом. Якщо ви використовуєте в основному статичні методи, ви, мабуть, не дуже хороший дизайн ОО. Це не обов'язково погано, але, як і всі запахи, це змусило б мене зупинитися і переоцінити. Це натякає на те, що ви могли б зробити кращий дизайн ОО, або що, можливо, вам слід піти в іншому напрямку і уникати ОО повністю для цієї проблеми.


2

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

У будь-якому випадку, синглтон дає вам можливість пізніше передавати щось на завод, щоб отримати інший екземпляр, і це змінює поведінку всієї вашої програми без рефакторингу. Зміна глобального класу статичних методів на щось із різними "резервними" даними або дещо іншою поведінкою (дочірній клас) є серйозною проблемою.

І статичні методи не мають подібних переваг.

Так що так, вони погані.


1

Поки не входить у дію не внутрішній стан, це нормально. Зверніть увагу, що, як правило, статичні методи повинні бути безпечними для потоків, тому, якщо ви використовуєте допоміжні структури даних, використовуйте їх безпечно для потоку.


1

Якщо ви знаєте, що вам ніколи не доведеться використовувати внутрішній стан С, це нормально. Якщо це коли-небудь зміниться в майбутньому, вам доведеться зробити метод нестатичним. Якщо він для початку нестатичний, ви можете просто ігнорувати внутрішній стан, якщо він вам не потрібен.


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

Статичні методи, як правило, поганий вибір навіть для коду без громадянства. Замість цього створіть клас-синглтон з цими методами, який створюється один раз і вводиться в ті класи, які хочуть використовувати методи. Такі класи легше знущатись і перевіряти. Вони набагато більше орієнтовані на об'єкти. За потреби ви можете обернути їх проксі-сервером. Статика значно ускладнює ОО, і я не бачу причин використовувати їх майже у всіх випадках. Не на 100%, а майже у всіх.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.