Класс с одним методом - лучший подход?

173

Скажем, у меня есть класс, предназначенный для выполнения одной функции. После выполнения функции ее можно уничтожить. Есть ли причина предпочитать один из этих подходов?

// 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 (). Я думаю больше о классах, которые выполняют какую-то конкретную, сложную задачу, но для этого требуется только один (публичный) метод.

JW.
источник

Ответы:

264

Я любил вспомогательные классы, заполненные статическими методами. Они сделали большую консолидацию вспомогательных методов, которые в противном случае могли бы привести к избыточности и адскому обслуживанию. Они очень просты в использовании, без инстанцирования, без утилизации, просто fire'n'forget. Я думаю, это была моя первая невольная попытка создать сервис-ориентированную архитектуру - множество сервисов без сохранения состояния, которые просто сделали свою работу и ничего больше. Однако по мере роста системы появляются драконы.

Полиморфизм
Допустим, у нас есть метод UtilityClass.SomeMethod, который радостно гудит. Внезапно нам нужно немного изменить функциональность. Большая часть функциональности одинакова, но, тем не менее, мы должны изменить несколько частей. Если бы это не был статический метод, мы могли бы создать производный класс и изменить содержимое метода по мере необходимости. Поскольку это статический метод, мы не можем. Конечно, если нам просто нужно добавить функциональность либо до, либо после старого метода, мы можем создать новый класс и вызвать внутри него старый класс - но это просто грубо.

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

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

Fosters blob
Поскольку статические методы обычно используются в качестве служебных методов, а служебные методы обычно имеют разные цели, мы быстро получим большой класс, заполненный непоследовательной функциональностью - в идеале каждый класс должен иметь одну цель в системе , Я бы предпочел проводить занятия в пять раз, если их цели четко определены.

Параметр ползучести
Начнем с того, что этот маленький симпатичный и невинный статический метод может принимать один параметр. По мере роста функциональности добавляется пара новых параметров. Вскоре добавляются дополнительные параметры, которые являются необязательными, поэтому мы создаем перегрузки метода (или просто добавляем значения по умолчанию на языках, которые их поддерживают). Вскоре у нас есть метод, который принимает 10 параметров. Только первые три действительно необходимы, параметры 4-7 являются необязательными. Но если указан параметр 6, 7-9 также должны быть заполнены ... Если бы мы создали класс с единственной целью сделать то, что сделал этот статический метод, мы могли бы решить это, приняв необходимые параметры в конструктор и позволяющий пользователю устанавливать необязательные значения через свойства или методы для одновременного задания нескольких взаимозависимых значений. Кроме того, если метод вырос до такой степени сложности,

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

Только ситхи имеют дело с абсолютами.
Конечно, есть исключения из моей неприязни к статическим методам. Истинные служебные классы, которые не представляют опасности для вздутия, являются отличными примерами для статических методов - System.Convert в качестве примера. Если ваш проект одноразовый без каких-либо требований для будущего обслуживания, общая архитектура на самом деле не очень важна - статическая или нестатическая, на самом деле не имеет значения - однако скорость разработки имеет значение.

Стандарты, стандарты, стандарты!
Использование методов экземпляра не мешает вам также использовать статические методы, и наоборот. Пока есть основания для дифференциации и она стандартизирована. Нет ничего хуже, чем просматривать бизнес-уровень с различными методами реализации.

Марк С. Расмуссен
источник
Как говорит Марк, полиморфизм, возможность передавать методы в качестве параметров «стратегии» и возможность конфигурировать / задавать параметры - все это причины для использования экземпляра . Например: ListDelimiter или DelimiterParser можно настроить относительно того, какие разделители использовать / принимать, обрезать ли пробельные символы из проанализированных токенов, заключать ли в скобки список, как обрабатывать пустой / нулевой список и т. Д.
Томас В.
4
«Как система растет ...» Вы рефакторинг?
Родни Гитцель
3
@ user3667089 Общие аргументы, необходимые для логического существования класса, которые я передал бы в конструктор. Они обычно используются в большинстве / всех методов. Определенные аргументы метода я передам в этом конкретном методе
Марк С. Расмуссен
1
На самом деле то, что вы делаете со статическим классом, возвращается к необработанному, не объектно-ориентированному C (хотя и управляемому из памяти) - все функции находятся в глобальном пространстве, нет this, вы должны управлять глобальным состоянием и т.д. если вам нравится процедурное программирование, сделайте это. Но учтите, что тогда вы потеряете многие структурные преимущества ОО.
инженер
1
Большинство из этих причин связано с плохим управлением кодом, а не с использованием статических методов. В F # и других функциональных языках мы везде используем статические методы (функции без состояния экземпляра) и не испытываем этих проблем. Тем не менее, F # обеспечивает лучшую парадигму для использования функций (функции первого класса, функции могут быть каррированы и частично применены), что делает их более осуществимыми, чем в C #. Большая причина использовать классы, потому что это то, для чего создан C #. Все конструкции Dependency Injection в .NET Core вращаются вокруг классов экземпляров с помощью deps.
Джастин Дж. Старк
89

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

Классы, которые существуют только для их методов, должны оставаться статическими.

jjnguy
источник
19
-1 «Классы, которые существуют только для их методов, должны оставаться статическими.»
Новобранец
8
@Rookian, почему ты не согласен с этим?
jjnguy
6
Примером может служить шаблон хранилища. Класс содержит только методы. Следование вашему подходу не позволяет использовать интерфейсы. => увеличить сцепление
Rookian
1
@ Rook, вообще люди не должны создавать классы, которые используются только для методов. В приведенном вами примере статические методы не очень хорошая идея. Но для простых утилитарных методов лучше использовать статический.
Jjnguy
9
Абсолютно правильно :) С вашим условием "для простых утилитарных методов" я с вами полностью согласен, но вы сделали общее правило в своем ответе, которое неверно, imo.
Новобранец
18

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

Рэй Джезек
источник
16

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

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

Билл Ящерица
источник
6

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

Nathen Silver
источник
5

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

Крис Кадмор
источник
4

Я бы предположил, что сложно ответить на основании предоставленной информации.

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

Конечно, трудно сказать точно, почему вам нужно создать отдельный класс только для этого одного метода. Является ли это типичной ситуацией в «классе коммунальных услуг», как большинство полагает? Или вы реализуете некоторый класс правил, которых может быть больше в будущем.

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

Ник
источник
3

Может ли ваш класс стать статичным?

Если так, то я бы сделал это классом Utilities, в который я поместил бы все свои однофункциональные классы.

Роберт
источник
2
Я несколько не согласен. В проектах, с которыми я работаю, ваш класс утилит может содержать сотни не связанных между собой методов.
Пол Томблин
3
@ Пол: В целом согласен. Я ненавижу видеть классы с десятками (или да, сотнями) совершенно не связанных между собой методов. Если нужно принять этот подход, по крайней мере, разделите их на меньшие, связанные наборы утилит (например, FooUtilities, BarUtilities и т. Д.).
Джон Руди
9
один класс - одна ответственность.
Андреас Петерссон
3

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

Джоэль Вительманн
источник
3

Для простых приложений и internalпомощников я бы использовал статический метод. Для приложений с компонентами мне нравится среда Managed Extensibility Framework . Вот выдержка из документа, который я пишу, чтобы описать шаблоны, которые вы найдете в моих 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);
pravprab
источник
Что если вам нужно вернуть что-либо, кроме объекта типа MyClass?
Билл Ящерица
13
Я считаю плохой практикой помещать логику выполнения в конструктор. Хорошее развитие - это семантика. Семантически конструктор существует для конструирования объекта, а не для выполнения других задач.
mstrobl
bill, если вам нужно вернуть значение, передайте ссылку на ret vvalue: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, если объект не нужен, зачем его создавать? и этот трюк может действительно помочь вам в некоторых случаях.
Если у объекта нет состояния, т. Е. Нет переменных, то его создание не приведет к выделению - ни в куче, ни в стеке. Вы можете думать об объектах в C ++ как о просто вызовах функций C со скрытым первым параметром, указывающим на структуру.
mstrobl
mstrobl, это как функция переменного тока на стероидах, потому что вы можете определить частные функции, которые может использовать ctor. Вы также можете использовать переменные-члены класса, чтобы помочь передавать данные в частные функции.
0

Еще один важный вопрос, который следует рассмотреть, - будет ли система работать в многопоточной среде и будет ли поточно-ориентированный метод иметь статический метод или переменные ...

Следует обратить внимание на состояние системы.

NZal
источник
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)

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

Хьюго Вуд
источник
0

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

user7545070
источник
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
    }
}
Марк Уолш
источник