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


172

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

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

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

Відповіді:


264

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

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

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

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

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

Повзання параметра
Для початку цей маленький милий і невинний статичний метод може взяти єдиний параметр. У міру зростання функціональності додається пара нових параметрів. Незабаром додаються додаткові параметри, які є необов'язковими, тому ми створюємо перевантаження методу (або просто додаємо значення за замовчуванням на мовах, які їх підтримують). Невдовзі у нас є метод, який приймає 10 параметрів. Потрібні лише перші три, параметри 4-7 необов’язкові. Але якщо вказано параметр 6, також потрібно заповнити 7-9 ... Якби ми створили клас з єдиною метою робити те, що зробив цей статичний метод, ми могли б вирішити це, взявши потрібні параметри в конструктор і дозволяє користувачеві встановлювати необов'язкові значення за допомогою властивостей або методів встановлювати кілька взаємозалежних значень одночасно. Крім того, якщо метод виріс до такої кількості складності,

Вимагаючи від споживачів створити екземпляр класів без причини
Один з найпоширеніших аргументів: чому вимагають, щоб споживачі нашого класу створили екземпляр для виклику цього єдиного методу, не використовуючи при цьому для цього примірника? Створення екземпляра класу - дуже дешева операція на більшості мов, тому швидкість не є проблемою. Додавання споживачам додаткової лінійки коду - це низька вартість закладання фундаменту набагато більш досяжного рішення в майбутньому. І нарешті, якщо ви хочете уникати створення екземплярів, просто створіть однотонну обгортку свого класу, яка дозволяє легко повторно використовувати - хоча це вимагає, щоб ваш клас був без громадянства. Якщо це не без громадянства, ви все одно можете створити статичні методи обгортки, які впораються з усім, одночасно надаючи всі переваги в довгостроковій перспективі. Нарешті,

Лише Sith займається абсолютами.
Звичайно, є винятки з моєї неприязні статичних методів. Справжні класи утиліти, які не становлять ніякого ризику розростання, є прекрасними випадками для статичних методів - System.Convert як приклад. Якщо ваш проект є одноразовим і не потребує подальшого обслуговування, загальна архітектура насправді не дуже важлива - статична або нестатична, насправді не має значення - швидкість розробки, однак, має.

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


Як каже Марк, поліморфізм, здатність передавати методи як параметри «стратегії» та вміти налаштовувати / встановлювати параметри - все це причини використовувати екземпляр . Наприклад: ListDelimiter або DelimiterParser можуть бути налаштовані на те, які роздільники використовувати / приймати, чи обрізати пробіли з проаналізованих жетонів, чи дужкою в списку, як обробити порожній / нульовий список тощо.
Thomas W

4
"Як система росте ..." Ви рефактор?
Родні Гітцель

3
@ user3667089 Загальні аргументи, необхідні для існування класу логічно, ті, які я передав би в конструкторі. Вони зазвичай використовуються в більшості / всіх методах. Аргументи конкретного методу я б передав у цьому конкретному методі.
Марк С. Расмуссен

1
Дійсно, те, що ви робите зі статичним класом, повертається до сирої, не об'єктно-орієнтованої C (хоча і керованої пам’яттю) - всі функції знаходяться в глобальному просторі, немає this, вам потрібно керувати глобальним станом тощо. якщо вам подобається процедурне програмування, зробіть це. Але цінуйте, що ви втрачаєте багато структурних переваг ОО.
Інженер

1
Більшість із цих причин пов'язані з поганим керуванням кодом, а не з використанням статичних методів. У F # та інших функціональних мовах ми використовуємо скрізь статичні методи (функції без стану екземплярів) скрізь, і у них немає проблем. Враховуючи це, F # забезпечує кращу парадигму використання функцій (функції першого класу, функції можна завивати і частково застосовувати), що робить їх більш доцільними, ніж у C #. Більша причина використовувати класи - це те, для чого створений C #. Всі конструкції введення залежностей у .NET Core обертаються навколо класів екземплярів з deps.
Джастін Дж Старк

89

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

Класи, які існують лише для їх методів, повинні залишатися статичними.


19
-1 "Класи, які існують лише для їх методів, повинні залишатися статичними."
Рокіян

8
@Rookian, чому ти не згоден з цим?
jjnguy

6
прикладом може бути шаблон сховища. Клас містить лише методи. Дотримання вашого підходу не дозволяє використовувати інтерфейси. => збільшення зчеплення
Rookian

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

9
Абсолютно правильно :) З вашим умовою "для простих корисних методів" я погоджуюся з вами повністю, але ви зробили загальне правило у своїй відповіді, що невірно.
Рокіян

18

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


16

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

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


6

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


5

Я дійсно не знаю, яка тут ситуація, але я би розглядав це як метод в одному з класів, до якого належать arg1, arg2 або arg3 - Якщо ви можете семантично сказати, що одному з цих класів належить метод.


4

Я б припустив, що важко відповісти на основі наданої інформації.

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

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

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


3

Чи можна зробити ваш клас статичним?

Якщо так, то я зробив би це клас "Утиліти", в який би я помістив усі свої однофункціональні класи.


2
Я дещо не згоден. У проектах, з якими я беру участь, у вашому класі Utilities можуть бути сотні неспоріднених методів.
Пол Томблін

3
@Paul: Загальна згода. Я ненавиджу бачити заняття з десятками (або так, сотнями) абсолютно неспоріднених методів. Якщо потрібно скористатися таким підходом, принаймні розділіть їх на більш дрібні, пов'язані набори утиліт (EG, FooUtilities, BarUtilities тощо).
Джон Руді

9
один клас - одна відповідальність.
Андреас Петерсон

3

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


3

Для простих додатків та internalпомічників я б застосував статичний метод. Для програм із компонентами я люблю Рамку керованої розширюваності . Ось уривок із документа, який я пишу, щоб описати шаблони, які ви знайдете в моїх API.

  • Послуги
    • Визначається I[ServiceName]Serviceінтерфейсом.
    • Експортується та імпортується за типом інтерфейсу.
    • Одинична реалізація забезпечується хост-програмою та споживається внутрішньо та / або розширеннями.
    • Методи сервісного інтерфейсу є безпечними для потоків.

Як надуманий приклад:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Я б просто все робив у конструкторі. так:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

або

MyClass my_object(arg1, arg2, arg3);

Що робити, якщо вам потрібно повернути що-небудь крім об’єкта типу MyClass?
Білл Ящірка

13
Я вважаю поганою практикою вкладати логіку виконання в конструктор. Хороший розвиток стосується семантики. Семантично існує конструктор для побудови об'єкта, а не для виконання інших завдань.
mstrobl

рахунок, якщо вам потрібно повернути значення, передайте посилання на ret vvalue: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, якщо об’єкт не потрібен, навіщо його створювати? і ця хитрість справді може допомогти вам у деяких випадках.

Якщо об'єкт не має стану, тобто немає vars, то його інстанція не призведе до виділення - ні на купі, ні на стеці. Ви можете розглядати об'єкти в C ++ як лише виклик функції C із прихованим першим параметром, що вказує на структуру.
mstrobl

mstrobl, це як функція змінного струму на стероїдах, тому що ви можете визначити приватні функції, якими може користуватися ctor. ви також можете використовувати змінні члена класу, щоб допомогти передавати дані приватним функціям.

0

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

Вам слід звернути увагу на стан системи.


0

Можливо, вам вдасться уникнути ситуації разом. Спробуйте зробити рефактор, щоб отриматиarg1.myMethod1(arg2, arg3) . Поміняйте arg1 на arg2 або arg3, якщо це має більше сенсу.

Якщо ви не маєте контролю над класом arg1, то прикрасьте його:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

Аргументація полягає в тому, що в OOP дані та методи обробки даних належать разом. Плюс ви отримуєте всі переваги, про які згадував Марк.


0

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


0

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

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.