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

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

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


person JW.    schedule 15.10.2008    source источник


Ответы (15)


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

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

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

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

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

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

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

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

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

person Mark S. Rasmussen    schedule 15.10.2008
comment
Как говорит Марк, полиморфизм, возможность передавать методы как параметры «стратегии» и возможность настраивать / устанавливать параметры - все это причины для использования экземпляра. Например: ListDelimiter или DelimiterParser могут быть настроены относительно того, какие разделители использовать / принимать, обрезать ли пробелы из проанализированных токенов, заключать ли список в скобки, как обрабатывать пустой / пустой список и т. Д. - person Thomas W; 28.04.2013
comment
По мере роста системы ... вы проводите рефакторинг? - person Rodney Gitzel; 21.02.2014
comment
@Mark S. Rasmussen Отличный пост. Вы бы передали аргументы методу или инициализировали аргументы в конструкторе? - person user3667089; 15.12.2015
comment
@ user3667089 Общие аргументы, необходимые для логического существования класса, которые я передал бы в конструктор. Обычно они используются в большинстве / во всех методах. Конкретные аргументы метода я бы передал в этом конкретном методе. - person Mark S. Rasmussen; 16.12.2015
comment
@ MarkS.Rasmussen Просто чтобы уточнить, в случае класса с одним методом вы передадите его как конструктор, потому что он используется для всех (этого единственного) метода? - person user3667089; 16.12.2015
comment
На самом деле то, что вы делаете со статическим классом, возвращается к необработанному, не объектно-ориентированному C (хотя и с управлением памятью) - все функции находятся в глобальном пространстве, нет this, вам нужно управлять глобальным состоянием и т. Д. Так что, если вам нравится процедурное программирование, сделайте это. Но примите во внимание, что в этом случае вы теряете многие структурные преимущества объектно-ориентированного программирования. - person Engineer; 03.03.2016
comment
Большинство этих причин связано с плохим управлением кодом, а не с использованием статических методов. В F # и других функциональных языках мы везде используем в основном статические методы (функции без состояния экземпляра) и не имеем этих проблем. Тем не менее, F # предоставляет лучшую парадигму для использования функций (функции первого класса, функции могут быть каррированы и частично применяться), что делает их более осуществимыми, чем в C #. Более серьезная причина использования классов заключается в том, что для этого создан C #. Все конструкции внедрения зависимостей в .NET Core вращаются вокруг классов экземпляров с зависимостями. - person Justin J Stark; 30.07.2019
comment
Этот ответ нарушает принципы KISS и YAGNI. Сегодня, когда в IDE широко доступны инструменты автоматического рефакторинга, мы должны придерживаться простоты и сохранять статичность до тех пор, пока действительно не потребуются полиморфизм, имитаторы и т. Д. - person fernacolo; 15.10.2019
comment
Может ли кто-нибудь предоставить версию этого ответа TL; Я читал этот ответ несколько раз, и я думаю, что он затрагивает ряд вопросов, но для меня он извилистый и немного непонятный. Может кто-нибудь перефразировать это, как ответ Джинги. Я подумал, что ответ Джингуи был кратким и хорошим объяснением его доводов. Похоже, что здесь нет четкого описания трех методов, а также плюсов и минусов. - person don; 13.05.2020

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

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

person jjnguy    schedule 15.10.2008
comment
-1 Классы, которые существуют только для своих методов, следует оставить статическими. - person Rookian; 13.08.2011
comment
@Rookian, почему ты с этим не согласен? - person jjnguy; 14.08.2011
comment
примером может служить шаблон репозитория. Класс содержит только методы. Следование вашему подходу не позволяет использовать интерфейсы. = ›Увеличить сцепление - person Rookian; 14.08.2011
comment
@Rook, вообще люди не должны создавать классы, которые используются только для методов. В приведенном вами примере статические методы - не лучшая идея. Но для простых служебных методов лучше всего использовать статический способ. - person jjnguy; 15.08.2011
comment
Абсолютно правильно :) С вашими условиями для простых служебных методов я полностью с вами согласен, но вы сделали общее правило в своем ответе, который, по-моему, неверен. - person Rookian; 15.08.2011
comment
Я не знаю о C # и Java, но в C ++ вы также можете объявить бесплатную функцию, что на самом деле часто предпочтительнее. Это хорошее чтение: gotw.ca/publications/mill02.htm - person Ela782; 10.06.2014
comment
@ Ela782, просто подумал, что упомяну, что C # и Java - это чистый O.O. языков и, следовательно, не имеют бесплатных функций. Отсюда необходимость в статических методах. - person OneHoopyFrood; 09.10.2014
comment
@colepanike О, спасибо, что освежили мои знания об этом. Прошло много времени с тех пор, как я в последний раз программировал на Java. - person Ela782; 09.10.2014
comment
Поскольку класс не представляет объект, нет смысла создавать его экземпляр. Один случай, с которым я бы не согласился, - это когда общий вспомогательный метод, который будет использоваться несколько раз (в одном потоке), требует подключения к базе данных, имеет смысл сохранить экземпляр класса со ссылкой на соединение , если вы не хотите постоянно создавать новые соединения или засорять параметры вашего метода, вводя ссылку на соединение. - person Anestis Kivranoglou; 08.09.2015

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

person Ray Jezek    schedule 15.10.2008

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

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

person Bill the Lizard    schedule 15.10.2008

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

person Nathen Silver    schedule 15.10.2008

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

person Chris Cudmore    schedule 15.10.2008

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

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

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

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

person Nick    schedule 15.10.2008

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

Если да, то я бы сделал его классом «Утилиты», в который я бы поместил все свои классы с одной функцией.

person Robert    schedule 15.10.2008
comment
Я несколько не согласен. В проектах, в которых я участвую, ваш класс Utilities может содержать сотни не связанных между собой методов. - person Paul Tomblin; 15.10.2008
comment
@ Пол: В целом согласен. Ненавижу видеть классы с десятками (или да, сотнями) совершенно не связанных между собой методов. Если нужно использовать этот подход, по крайней мере, разделите их на более мелкие связанные наборы утилит (EG, FooUtilities, BarUtilities и т. Д.). - person John Rudy; 15.10.2008
comment
один класс - одна ответственность. - person Andreas Petersson; 15.10.2008

Если этот метод не имеет состояния и вам не нужно передавать его, тогда имеет смысл определить его как статический. Если вам ДЕЙСТВИТЕЛЬНО нужно передать метод, вы можете рассмотреть возможность использования делегировать, а не один из других предложенных вами подходов.

person Joel Wietelmann    schedule 15.10.2008

Для простых приложений и internal помощников я бы использовал статический метод. Что касается приложений с компонентами, мне нравится Managed Extensibility Framework. Вот отрывок из документа, который я пишу, чтобы описать шаблоны, которые вы найдете в моих API.

  • Services
    • Defined by an I[ServiceName]Service interface.
    • Экспортируется и импортируется по типу интерфейса.
    • Единственная реализация предоставляется хост-приложением и используется внутри и / или расширениями.
    • Методы в интерфейсе службы являются потокобезопасными.

В качестве надуманного примера:

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
        }
    }
}
person Sam Harwell    schedule 15.01.2010

Я бы просто все сделал в конструкторе. вот так:

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

or

MyClass my_object(arg1, arg2, arg3);
person Community    schedule 15.10.2008
comment
Что, если вам нужно вернуть что-нибудь, кроме объекта типа MyClass? - person Bill the Lizard; 15.10.2008
comment
Я считаю плохой практикой помещать логику выполнения в конструктор. Хорошая разработка - это семантика. Семантически конструктор существует для создания объекта, а не для выполнения других задач. - person mstrobl; 15.10.2008
comment
счет, если вам нужно возвращаемое значение, передайте ссылку на ret vvalue: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, если объект не нужен, зачем его создавать? и этот трюк может действительно помочь вам в некоторых случаях. - person ; 15.10.2008
comment
Если объект не имеет состояния, то есть переменных, то его создание не приведет к выделению памяти - ни в куче, ни в стеке. Вы можете рассматривать объекты в C ++ как просто вызовы функций C со скрытым первым параметром, указывающим на структуру. - person mstrobl; 15.10.2008
comment
mstrobl, это как функция c на стероидах, потому что вы можете определять частные функции, которые может использовать ctor. вы также можете использовать переменные-члены класса, чтобы передавать данные частным функциям. - person ; 15.10.2008
comment
Ваш аргумент заключался в том, что если вам не нужен объект, зачем его создавать. Когда вы говорите о состоянии, вы говорите о необходимости объекта. Использование конструктора для чего-либо, кроме построения, - это хитрость, imho. Подумайте о функторах. Если вам нужно использовать объект неявно, сделайте это: MyClass (). Foo () - person mstrobl; 15.10.2008

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

Обратите внимание на состояние системы.

person NZal    schedule 27.07.2012

Возможно, вам удастся полностью избежать ситуации. Попробуйте провести рефакторинг, чтобы получить 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)

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

person Hugo Wood    schedule 31.01.2013

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

person user7545070    schedule 10.02.2017

В зависимости от того, хотите ли вы только что-то сделать или сделать и вернуть что-то, вы можете сделать это:

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
    }
}
person Mark Walsh    schedule 14.05.2020