Статические фабричные методы против конструкторов экземпляров (обычных)?

В языке, где доступны и то, и другое, вы бы предпочли видеть конструктор экземпляра или статический метод, который возвращает экземпляр?

Например, если вы создаете String из char[]:

  1. String.FromCharacters(chars);

  2. new String(chars);


person Dan Goldstein    schedule 11.10.2008    source источник
comment
В заголовок добавлен метод, так как в противном случае он предполагает выбор между конструктором экземпляра или статическим конструктором. (Текст понятен, но название вызвало у меня некий Ха? вопрос :)   -  person Jon Skeet    schedule 11.10.2008


Ответы (11)


В Effective Java, 2-е издание Джошуа Блох определенно рекомендует первый. Есть несколько причин, которые я могу вспомнить, и, несомненно, некоторые из них я не могу:

  • Вы можете дать методу осмысленное имя. Если у вас есть два способа создания экземпляра, оба из которых принимают целое число, но имеют разные значения для этого целого числа, использование обычного метода делает вызывающий код намного более читабельным.
  • Следствие первого — у вас могут быть разные фабричные методы с одним и тем же списком параметров
  • Вы можете вернуть null для случаев "потенциально ожидаемого сбоя", тогда как конструктор всегда либо вернет значение, либо выдаст исключение.
  • Вы можете вернуть тип, отличный от объявленного (например, вернуть производный класс)
  • Вы можете использовать его как фабрику, чтобы потенциально возвращать ссылку на один и тот же объект несколько раз.

Недостатки:

  • В настоящее время это не так идиоматично - разработчики больше привыкли видеть "новое"
  • Если вы видите «новый», вы знаете, что получаете новый экземпляр (по модулю странность, о которой я недавно упоминал)
  • Вам нужно сделать соответствующие конструкторы доступными для подклассов
  • В C# 3 вызовы конструктора могут компактно задавать поля/свойства с помощью выражений инициализатора объекта; эта функция не применяется к вызовам статических методов
person Jon Skeet    schedule 11.10.2008
comment
Читая пункт 2 книги Effective Java, легко может сложиться впечатление, что Джошуа Блох выступает за использование статических фабричных методов в качестве выбора по умолчанию. Но я не думаю, что на самом деле это так, по крайней мере, в 2002 году, когда он сделал это интервью с Биллом Веннерсом, которое я процитировал в своем ответе ниже. - person MB.; 15.06.2011
comment
ре. ваша точка зрения на С#, я использовал этот шаблон в прошлом; См. мой ответ здесь, который предлагает обходной путь к проблеме компактного инициализатора (ну, в любом случае заменяет его чем-то, что также компактно!) /a/10171659/705589 - person Mikey Hogarth; 16.04.2012
comment
Еще одно преимущество заключается в том, что вы можете легче связать/составить/повторно использовать статические фабричные методы по сравнению с ctors. - person Mahdi; 15.09.2020

Я пишу конструктор, когда создание экземпляра не имеет побочных эффектов, т.е. когда единственное, что делает конструктор, — это инициализирует свойства. Я пишу статический метод (и делаю конструктор закрытым), если создание экземпляра делает что-то, чего вы обычно не ожидаете от конструктора.

Например:

public class Foo
{
   private Foo() { }

   private static List<Foo> FooList = new List<Foo>();
   public static Foo CreateFoo()
   {
      Foo f = new Foo();
      FooList.Add(f);
      return f;
   }
}

Поскольку я придерживаюсь этого соглашения, если я вижу

Foo f = Foo.CreateFoo();
Bar b = new Bar();

при чтении моего кода у меня совсем другой набор ожиданий относительно того, что делает каждая из этих двух строк. Этот код не говорит мне, что отличает создание Foo от создания Bar, но он говорит мне, что мне нужно посмотреть.

person Robert Rossney    schedule 11.10.2008
comment
Хорошая мысль об аномальной обработке. Лично мне не нравится идея использования статического метода вместо конструктора по умолчанию. Это повторяется и идет против течения языка. Я бы предпочел реорганизовать ненормальную обработку и использовать новое ключевое слово, если это возможно. - person Dan Goldstein; 12.10.2008
comment
@Robert, это не делает недействительным использование статики в качестве аргумента по умолчанию. Вы по-прежнему можете отличить статический конструктор от специального статического конструктора (который вам нужно посмотреть), используя соглашения об именах. Например. Foo.New() в качестве замены по умолчанию для new и Foo.FromAbc() и т. д. для особых. - person Pacerier; 25.06.2014

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

Так или иначе, я наткнулся на цитату из интервью Билла-Веннера с Джошем Блохом, которую я нашел полезный:

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

Некоторые люди были разочарованы, найдя этот совет в моей книге. Они прочитали его и сказали: «Вы так сильно настаивали на общедоступных статических фабриках, что мы должны просто использовать их по умолчанию». Я думаю, что единственным реальным недостатком этого является то, что это немного смущает людей, которые привыкли использовать конструкторы для создания своих объектов. И я полагаю, что это дает немного меньше визуальной подсказки в программе. (Вы не видите нового ключевого слова.) Также немного сложнее найти статические фабрики в документации, потому что Javadoc группирует все конструкторы вместе. Но я бы сказал, что все должны постоянно учитывать статические фабрики и использовать их, когда это уместно.

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

*К сожалению, это исследование было сосредоточено не столько на статических фабричных методах, сколько на фабричном шаблоне (где существует отдельный фабричный объект для создания новых экземпляров), поэтому я не уверен, что можно действительно сделать вывод, что статические фабричные методы смущают многих программистов. Хотя исследование действительно произвело на меня впечатление, что они часто будут.

person MB.    schedule 15.06.2011

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

person Dan Goldstein    schedule 11.10.2008

На ICSE'07 есть документ, в котором изучалось удобство использования конструкторов и фабричных шаблонов. Хотя я предпочитаю фабричные шаблоны, исследование показало, что разработчики медленнее находили правильный фабричный метод.

http://www.cs.cmu.edu/~NatProg/papers/Ellis2007FactoryUsability.pdf

person Uri    schedule 11.10.2008
comment
К сожалению, это хороший ответ. Я готов сделать размещение без нового ключевого слова, но возможность обнаружения очень важна. Я бы не хотел, чтобы кто-то выполнял собственную обработку для создания теоретического объекта String, потому что он не видел конструктора. - person Dan Goldstein; 12.10.2008
comment
Я согласен, проблема того, как убедиться, что люди обнаруживают статику, является важной. Второй автор этой статьи в настоящее время создает какой-то инструмент для этого в рамках своей докторской работы, будет интересно посмотреть, чем это закончится. - person Uri; 12.10.2008
comment
@Uri, это, конечно, медленнее в настоящее время, потому что еще нет органа стандартов, создающего правильные соглашения об именах для статических конструкторов. Сравните это с широко известным соглашением new в обычных конструкторах. - person Pacerier; 25.06.2014

Статический метод. Затем вы можете вернуть ноль, а не генерировать исключение (если только это не ссылочный тип)

person Darren Kopp    schedule 11.10.2008

Я предпочитаю конструктор экземпляров просто потому, что это имеет для меня больше смысла, и меньше потенциальной двусмысленности с тем, что вы пытаетесь выразить (например: что, если FromCharacters - это метод, который принимает один символ). Хотя конечно субъективно.

person Nick    schedule 11.10.2008

Я лично предпочитаю видеть обычный конструктор, так как конструкторы должны использоваться для построения. Однако, если есть веская причина не его использовать, например, если FromCharacters явно заявил, что он не выделял новую память, это было бы целесообразно. «Новое» в призыве имеет значение.

person nimish    schedule 11.10.2008

По-разному. Для языков, в которых использование конструктора экземпляра является «нормальным», я обычно использовал бы его, если бы у меня не было веских причин не делать этого. Это следует принципу наименьшего удивления.

Кстати, вы забыли еще один распространенный случай: конструктор null/по умолчанию в паре с методом инициализации.

person ejgottl    schedule 11.10.2008
comment
Да, я не подумал об этом случае. Я думаю, что он уступает двум представленным, потому что ему нужны строки кода. Я мог бы отредактировать вопрос, чтобы включить его. - person Dan Goldstein; 12.10.2008
comment
@ejgottl, что вы подразумеваете под нулевым конструктором в сочетании с инициализацией? - person Pacerier; 25.06.2014
comment
Конструктор, который не принимает никаких параметров и, возможно, только выделяет память, оставляя объект неинициализированным. В паре с методом(ами) инициализации. Если я правильно помню, именно так объекты Ruby реализуются в C. - person ejgottl; 21.08.2014

Как перефразировал Джон Скит Джоша Блоха, существует ряд причин, по которым статический фабричный метод во многих случаях предпочтительнее конструктора. Я бы сказал, что если класс простой, без дорогостоящей настройки или сложного использования, оставайтесь с идиоматическим конструктором. Современные JVM делают создание объектов чрезвычайно быстрым и дешевым. Если класс может быть подклассом или вы можете сделать его неизменяемым (большое преимущество для параллельного программирования, значение которого только возрастает), используйте фабричный метод.

Еще один совет. Не называйте фабричный метод Foo.new* или Foo.create*. Метод с такими именами всегда должен возвращать новый экземпляр, и при этом упускается одно из больших преимуществ фабричного метода. Лучшее соглашение об именах — Foo.of* или Foo.for*. библиотека Google Guava (ранее Библиотека коллекций Google) отлично справляется с это, имхо.

person Dov Wasserman    schedule 12.10.2008
comment
Не могли бы вы немного пояснить, почему of* и for* лучше, чем new* или create*? Для меня важно правильно называть вещи, поэтому я хотел бы узнать здесь что-то новое. - person stakx - no longer contributing; 09.05.2013
comment
@stakx: если вы используете new * или create *, вы вынуждены предоставить новый экземпляр, а использование предложенных имен позволяет повторно использовать существующие. - person Igor Rodriguez; 30.09.2013

Конечно, у статических фабричных методов есть несколько преимуществ перед конструкторами.

  1. фабричные методы помогают нам писать гибкий код. Тогда как конструкторы сделали его тесно связанным.
  2. Статические фабричные методы улучшают читаемость, поскольку позволяют создавать разные экземпляры.
  3. Вы можете вернуть кешированный объект. Например, getInstance() в синглтоне.
person Sumanth Varada    schedule 28.01.2019