Чи не можу я просто використовувати всі статичні методи?


64

Яка різниця між двома методами UpdateSubject нижче? Я вважав, що статичні методи краще, якщо ви просто хочете оперувати сутностями. У яких ситуаціях слід звертатися з нестатичними методами?

public class Subject
{
    public int Id {get; set;}
    public string Name { get; set; }

    public static bool UpdateSubject(Subject subject)
    {
        //Do something and return result
        return true;
    }
    public bool UpdateSubject()
    {
        //Do something on 'this' and return result
        return true;
    }
}

Я знаю, що я отримаю багато ударів від громади за це справді дратує питання, але я не міг перестати себе задавати.

Чи стає це недоцільним при роботі зі спадщиною?

Оновлення: це
відбувається зараз на нашому робочому місці. Ми працюємо над 6-місячним веб-додатком asp.net з 5 розробниками. Наш архітектор вирішив використовувати всі статичні методи для всіх API. Його міркування, що є статичними методами, мають невелику вагу, і це приносить користь веб-програмам, зменшуючи завантаження сервера.


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

13
@Job: Я не дуже розумію, наскільки це функціонально - це процедурно.
Aaronaught

2
@Job: Цей клас не змінюється.
Aaronaught

3
@DonalFellows: Звичайно, ви можете, C # і F # - це змішана парадигма. Але факт залишається фактом: статичні методи! = Функціональне програмування. FP означає передачу / ланцюг функцій, незмінні типи даних, уникнення побічних ефектів тощо. Це абсолютно протилежне тому, що робить фрагмент коду в ОП.
Aaronaught

4
"Його міркування є статичними методами - це невелика вага, і це приносить користь веб-програмам, зменшуючи завантаження сервера". Це неправильно. Зниження завантаження сервера?
mamcx

Відповіді:


87

Я піду з найбільш очевидними проблемами статичних методів із явними параметрами "це":

  1. Ви втрачаєте віртуальну розсилку та згодом поліморфізм. Ніколи не можна перекрити цей метод у похідному класі. Звичайно, ви можете оголосити метод new( static) у похідному класі, але будь-який код, який має доступ до нього, повинен бути обізнаний у всій ієрархії класів і робити явну перевірку та кастинг, саме цього слід уникати OO .

  2. Начебто розширення №1, ви не можете замінити екземпляри класу інтерфейсом , оскільки інтерфейси (у більшості мов) не можуть оголосити staticметоди.

  3. Непотрібна багатослівність. Що легше читати: Subject.Update(subject)чи просто subject.Update()?

  4. Перевірка аргументів. Знову залежить від мови, але багато хто складе неявну перевірку, щоб переконатися, що thisаргумент не nullдля того, щоб запобігти помилковій помилці посилання від створення небезпечних умов виконання (вид перевищення буфера). Якщо не використовуються методи екземпляра, вам доведеться явно додавати цю перевірку на початку кожного методу.

  5. Це заплутано. Коли звичайний, розумний програміст бачить staticметод, він, природно, припускає, що він не вимагає дійсного примірника (якщо він не потребує декількох примірників, як метод порівняння або рівності, або, як очікується, зможе працювати з nullпосиланнями) . Бачачи статичні методи, використані таким чином, змусить нас зробити подвійний або, можливо, потрійний, і після 4-го або 5-го разу ми будемо піддаватися стресу і гніву, і Бог допоможе вам, якщо ми знаємо вашу домашню адресу.

  6. Це форма дублювання. Що насправді відбувається, коли ви викликаєте метод екземпляра, це те, що компілятор або час виконання шукає метод у таблиці методів типу та викликає його, використовуючи thisяк аргумент. Ви в основному повторно реалізуєте те, що вже робить компілятор. Ви порушуєте DRY , повторюючи один і той же параметр знову і знову в різних методах, коли це не потрібно.

Важко уявити якусь вагому причину замінити методи екземпляра статичними методами. Будь ласка, не варто.


5
+1 лише для вашого остаточного рядка Я б хотів, щоб набагато більше людей подумали "метод екземпляра", перш ніж вони подумають "статичний метод".
Стюарт Лейланд-Коул

4
+1. Я додам, що статичні методи роблять важко неможливим написання тестів, так як в більшості випадків ви не можете знущатися над ними: раз будь-яка частина вашого коду залежить від статичного методу, ви не можете замінити його символом no- оп у ваших тестах. Яскравий приклад в світі .NET б такі класи , як File, FileInfoі DirectoryInfo(а насправді, є бібліотеки , які приховують їх за інтерфейсами , які потім можуть бути введені по мірі необхідності і знущався в тестах).
см

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

@sm це просто неправда. якщо у вас є жорстка залежність від будь-якого, статичного чи ні, це не може бути в одиничному тесті, то це непереривний період. статичне це не викликає. статичний метод може бути введений так само, як і клас, що представляє db-з'єднання. ви припускаєте, що "статична" означає "статичні плюс жорсткі приховані залежності, що торкаються глобального стану та зовнішніх ресурсів". це не чесно.
сара

@kai спробуйте ввести статичний метод, який робить Y замість X, якими б не були X та Y. Вам не потрібно приймати залежності від нічого зовнішнього, яке потрібно прикрутити. Можливо, вам просто цікаво протестувати якийсь аспект, який не вимагає тривалого обчислення X для виконання. Як ввести свій фантазійний статичний метод, не створюючи обгортку потім? Передача функцій не підтримується кожною мовою. Статичні методи мають свої випадки використання, і я їх використовую, коли це має сенс . Це, як це завжди буває, випадок "лише тому, що ти не можеш означати, що ти повинен".
sm

13

Ви в значній мірі описали, як саме ви робите OOP в C, в якому доступні лише статичні методи, і "це" є структурним покажчиком. І так, ви можете виконати поліморфізм часу в C, вказавши покажчик функції під час побудови, який зберігається як один елемент структури. Методи інстанції, доступні іншими мовами, - це лише синтаксичний цукор, який по суті робить саме те саме під кришкою. Деякі мови, як-от python, знаходяться на півдорозі, де параметр "self" прямо вказаний, але спеціальний синтаксис для його виклику обробляє спадщину та поліморфізм для вас.


FWIW, за допомогою C ви можете виконувати віртуальну диспетчеру так: obj->type->mthd(obj, ...);і це по суті схоже на те, що відбувається з віртуальною відправленням іншими мовами (але поза кадром).
стипендіати

@Karl Чи можете ви надати якусь письмову посилання, щоб почати копати глибше?
Хорхе Лавін

Ви можете розглянути GObject як хорошу опорну реалізацію.
Карл Білефельдт

10

Проблема зі статичним методом виникає в той момент, коли вам потрібен підклас.

Статичні методи не можуть бути замінені в підкласах, отже, нові класи не можуть забезпечити нові реалізації методів, роблячи їх менш корисними.


2
Це не обов'язково проблема. Деякі мови надають інші механізми (невіртуючі методи на C ++ та C #) для того, щоб навмисно здійснити одне й те саме - C # навіть надає "запечатані" методи для подальшого розширення цієї ідеї! Очевидно, ви б цього не зробили лише для чортви, але це хороша техніка, яку слід знати ...
Shog9

@Steve, це не замінює, так як ви все ще можете викликати обидва способи безпосередньо.

@Steve, в Java статичні методи не можна перекрити.

@ Thorbjørn - по-перше, питання не позначається Java або будь-якою іншою мовою OOP. Що ще важливіше, я не сказав, що ви можете перекрити статичну функцію члена. Я намагався використати аналогію, щоб зробити точку зору. Як я явно не вдалося, коментарі видалено.
Steve314

2
У певному сенсі це все ще "нові реалізації методів". Тільки не в стандартному сенсі ООП. Це трохи схоже на те, що я кажу, що "твоє формулювання теж не ідеальне, nur-nur-na-nur-nur" ;-)
Steve314

7

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

По-іншому, staticключове слово спричиняє, що декларація є частиною визначення класу, тому воно стає єдиною опорною точкою для всіх екземплярів класу (об'єкти, інстанційовані з класу).

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

Загалом, ви повинні використовувати staticкласи та методи лише тоді, коли ви створюєте утилітні методи, які приймають один чи більше параметрів, і повертають об'єкт якогось без будь-яких побічних ефектів (тобто змін змінних станів у класі)


1
Я тоненько ОП розуміє, що staticозначає, він запитує, чому б просто не оголосити все статичним.
Ред С.

1
Він буде проходить в екземплярі Subject- але в якості явного параметра, замість неявного thisпараметра.
Steve314

Ну так ... але я не бачу вашої точки зору.
Ред С.

@Ed - лише те, що ви можете зробити все що завгодно через явний параметр, який ви могли отримати через неявний thisпараметр. Існують відмінності в очікуванні, але поза спадщиною немає реальної різниці в тому, що ти можеш зробити. Наприклад, до цих нестатичних членів можна отримати доступ - через явний параметр.
Steve314

Я працював за припущенням, що ви не можете потрапити на приватних членів аргументу в C # навіть за допомогою статичного методу, оголошеного в тому ж класі (як ви можете в C ++). Не впевнений, чому я так подумав, але помилявся.
Ед С.

3

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

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

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


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

making ... unit testing much easier.Ні, це не буде. Це робить будь-який код, який викликає статичні методи, неможливим для одиничного тестування, оскільки ви не можете його ізолювати від статичного методу. Виклик статичного методу стає жорсткою залежністю до цього класу.
StuperUser

2

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

Як ваш приклад синтаксису підказує Java або C #, я думаю, що Thorbjørn Ravn Andersen має рацію вказати на головну проблему (з пізнім прив’язкою). При статичному методі не існує "спеціального" параметра для пізнього прив'язки, на якому не можна базуватися, тому ви не можете мати пізнє прив'язування.

Насправді статичний метод - це лише функція в модулі, цей модуль має те саме ім’я, що і клас - насправді це зовсім не метод OOP.


2

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

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


3
Ви могли б це зробити , якщо ви пропускання objectдо staticспособу, і повертаючи новий object. Функціональні програмісти роблять це постійно.
Роберт Харві

питання: чи вважаєте ви Mathстатичний клас "складним без виграшу", чи хотіли б ви його використовувати, якби всі типи чисел мали віртуальні методи, що визначають абсолютне значення, заперечення, додавання, квадратування, довільні корені тощо? Я не думаю, що так. об'єкти акуратні, коли вам потрібно зберігати прихований змінений стан, де вам потрібно застосувати деякі інваріанти. З’єднуючи разом кожен доцільний метод, який працює над типом, у сам тип, ТОМЕ це те, що призводить до складного і нездійсненного коду. Я погоджуюся з Робертом, ви можете хотіти подивитися, як функціонують функціональні програмісти, це може бути справжнє відкриття очей!
сара

@kai Як я вже сказав, дуже багато. Клас Math є розумним винятком, хоча приєднання його до числових типів не було б поганою ідеєю.
Лорен Печтел

2

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

"Наш архітектор вирішив використовувати всі статичні методи для всіх API. Його міркування - статичні методи - це невелика вага, і це приносить користь веб-програмам, зменшуючи завантаження сервера ."

(сміливий акцент - мій)

Мій 2c у цій частині питання:

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

Гра трохи захисника диявола щодо цього: ми можемо піти ще далі і сказати такі речі, як:

  • це може стати по-справжньому поганим, якщо створити екземпляр для кожного виклику методу (instance) (на відміну від того, щоб просто залишити його статичним і назвати його таким)

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

По правді кажучи, ІМХО, наведені вище пункти (і загальний підхід "використовуємо статику, тому що вони швидші") - це солом'яні аргументи / оцінки:

  • якщо код хороший і, більш конкретний для цього аргументу, якщо екземпляри створюються лише при необхідності (і знищуються оптимально), то ви не отримаєте додаткових накладних витрат від використання методів insance, коли це доречно (тому що якщо ви створюйте лише потрібні об'єкти, вони були б створені, навіть якби цей метод був оголошений статичним, де б то не було = той самий екземпляр над головою)

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


2

Це дуже цікаве запитання, яке, як правило, має дуже різні відповіді залежно від громади, яку ви йому задаєте. Інші відповіді, схоже, є зміщенням C # або java: мова програмування, яка (в основному) є чисто орієнтованою на об'єкт і має своєрідне ідіоматичне уявлення про те, що таке орієнтація на об'єкт.

В інших спільнотах, таких як C ++, орієнтація на об'єкт інтерпретується з більшою ліберальністю. Деякі добре знають фахівці вивчали цю тему і, власне, роблять висновок, що вільні функції покращують інкапсуляцію (акцент - мій):

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

Скотт Майерс

Для тих, хто не знає термінологію C ++:

  • член-функція = метод
  • нечленова функція = статичний метод
  • non-friend = використовувати лише загальнодоступний API
  • non-member non-friend function = статичний метод, який використовує лише загальнодоступний API

Тепер, щоб відповісти на ваше запитання:

Чи не можу я просто використовувати всі статичні методи?

Ні, не завжди слід використовувати статичні методи.

Але ви повинні використовувати їх, коли вам потрібно використовувати лише загальнодоступний API класу.


-3

на Java ...

Статичні методи не перезаписуються, а ховаються замість цього, і це було б великою різницею (проблема?) У випадку розширення класу.

З іншого боку, я помітив, що Java-програмісти мають тенденцію думати, що ВСЕ МОЖЕ бути об'єктом, не розуміючи чому, і я не згоден.

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

кілька хороших прикладів використання статичних методів - це незалежні функції без побічних ефектів.

також побачити ключове слово синхронізоване ...


2
Як ховається цей статичний метод і де виникають ці відмінності та проблеми при розширенні класу? Як синхронізовано вписується в це? Де виникають проблеми статики та успадкування? Чи можете ви надати кілька хороших і поганих статичних методів у основних бібліотеках Java і пояснити, чому так відбувається?
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.