Яка краща практика - хелперні методи як екземпляр чи статичний?


48

Це питання є суб'єктивним, але мені було просто цікаво, як більшість програмістів підходить до цього. Зразок нижче наведений у псевдо-C #, але це має стосуватися також Java, C ++ та інших мов OOP.

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

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

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

Що ви віддаєте перевагу у власному коді та які причини для цього?


3
Я б видалив C ++ з цього питання: в C ++, очевидно, ви зробите це безкоштовним методом, тому що немає сенсу його довільно сховати в об’єкт: він вимикає ADL.
Матьє М.

1
@MatthieuM: Безкоштовний метод чи ні, це не має значення. Намір мого запитання полягав у тому, щоб запитати, чи є якісь переваги у оголошенні допоміжних методів як екземплярів. Тож у C ++ вибором може бути метод екземпляра проти безкоштовного методу / функції. У вас є хороший пункт про ADL. Так ви кажете, що ви віддаєте перевагу використовувати безкоштовні методи? Дякую!
Іліан

2
для C ++ рекомендуються безкоштовні методи. Вони збільшують інкапсуляцію (опосередковано, як лише доступ до загальнодоступного інтерфейсу) і все ще грають приємно через ADL (особливо в шаблонах). Однак я не знаю, чи це стосується Java / C #, C ++ сильно відрізняється від Java / C #, тому я очікую, що відповіді будуть різними (і, таким чином, я не думаю, що запитувати три мови разом мають сенс).
Матьє М.


1
Не намагаючись нікого погіршити, я ніколи не чув вагомих причин не використовувати статичні методи. Я використовую їх, де можу, оскільки вони роблять більш очевидним, що робить метод.
Шрідхар Сарнобат

Відповіді:


23

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

Якщо вони є складними, то SOLID застосовується, більш конкретно, S , в I і D .

Приклад:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Це стосується вашої проблеми. Ви можете зробити makeEveryBodyAsHappyAsPossibleстатичний метод, який прийме необхідні параметри. Ще один варіант:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Тепер OurHouseне потрібно знати про тонкощі правил розповсюдження файлів cookie. Це повинен бути лише об'єкт, який реалізує правило. Реалізація абстрагується від об'єкта, який несе єдину відповідальність - застосувати правило. Цей об'єкт може бути випробуваний ізольовано. OurHouseможна перевірити за допомогою простого макету CookieDistributor. І ви можете легко вирішити змінити правила розповсюдження файлів cookie.

Однак подбайте про те, щоб ви не перестаралися. Наприклад, наявність складної системи з 30 класів виступає як реалізація CookieDistributor, коли кожен клас просто виконує крихітне завдання, насправді не має сенсу. Моя інтерпретація СРП полягає в тому, що вона не лише диктує, що кожен клас може мати лише одну відповідальність, але також і те, що за один клас повинна відповідати одна відповідальність.

Що стосується примітивів чи об'єктів, які ви використовуєте як примітиви (наприклад, об’єкти, що представляють точки у просторі, матриці чи щось таке), статичні класи помічників мають багато сенсу. Якщо у вас є вибір, і це дійсно має сенс, тоді ви можете насправді розглянути можливість додавання методу до класу, що представляє дані, наприклад, для a Pointмає сенс мати addметод. Знову ж таки, не перестарайтеся.

Тож залежно від вашої проблеми існують різні способи її вирішення.


18

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

Хоча в цьому підході є серйозне обмеження: такі методи / класи не можуть бути легко знущаються (хоча AFAIK принаймні для C # є знущальні рамки, які дозволяють досягти навіть цього, але вони не є звичайним явищем, і принаймні деякі з них є комерційний). Отже, якщо метод помічника має будь-яку зовнішню залежність (наприклад, БД), що робить його - таким чином його абонентам - важким для одиничного тестування, краще оголосити його нестатичним . Це дозволяє вводити залежність, тим самим полегшуючи абонентів методу простішим тестуванням.

Оновлення

Уточнення: вище йдеться про утилітні класи, які містять лише допоміжні методи низького рівня та (як правило) відсутність стану. Допоміжні методи всередині державних класів, що не є корисними, - це інше питання; вибачення за неправильне тлумачення ОП.

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


Ви не можете просто передати залежність від статичного методу помічника?
Іліан

1
@JackOfAllTrades, якщо єдиною метою методу є боротьба із зовнішнім ресурсом, навряд чи є якийсь момент, який вводить цю залежність. В іншому випадку це може бути рішенням (хоча в останньому випадку метод, ймовірно, порушує Принцип єдиної відповідальності, тому є кандидатом на рефакторинг).
Péter Török

1
статику легко «глузувати» - Microsoft Moles або Fakes (залежно від VS2010 або VS2012 +) дозволяє замінити статичний метод на власну реалізацію у ваших тестах. Старі глузуючі рамки роблять застарілими. (це також робить традиційні глузування, і теж може глузувати з конкретних занять). Це безкоштовно від MS через менеджер розширень.
gbjbaanb

4

Що ви віддаєте перевагу робити у власному коді

Я віддаю перевагу №1 ( this->DoSomethingWithBarImpl();), якщо, звичайно, допоміжний метод не потребує доступу до даних / реалізації екземпляра static t_image OpenDefaultImage().

і які ваші причини для цього?

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

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

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Якби №2 було хорошим загальним рішенням (наприклад, за замовчуванням) у кодовій базі, я б переймався структурою дизайну: чи занадто багато класів роблять? чи недостатньо інкапсуляції чи недостатнього повторного використання? чи достатньо високий рівень абстракції?

нарешті, №2 здається зайвим, якщо ви використовуєте мови OO, де підтримується мутація (наприклад, constметод).

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