общедоступный статический фабричный метод

Прежде всего, пожалуйста, простите меня, если это действительно глупый вопрос, я просто пытаюсь выучить этот язык до его сути. Я читаю «Эффективную Java», и в самой первой главе рассказывается о статических фабричных методах и конструкторах. Их плюсы и минусы. Несколько вещей, которые меня смущают:

  1. класс объекта, возвращаемый статическим фабричным методом, не является общедоступным — что именно это означает?
  2. В отличие от конструкторов, статические фабричные методы не обязаны создавать новый объект при каждом вызове. Как это происходит? Я вызываю фабричный метод только для получения нового объекта, и мы ставим галочку в фабричном методе для проверки того, существует ли объект?

Спасибо.


person t0mcat    schedule 02.11.2010    source источник
comment
+1 за глубокое погружение в практику разработки программного обеспечения и хороший вопрос.   -  person Brian Driscoll    schedule 02.11.2010
comment
+1 за вопрос, ПОЧЕМУ вместо того, чтобы просто программировать, написал. Привычка хорошего кодера.   -  person TreyE    schedule 02.11.2010
comment
Знание того, где найти несколько примеров из реальной жизни, и проверка их исходного кода может помочь в понимании дизайна. узоры лучше.   -  person BalusC    schedule 02.11.2010


Ответы (5)


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

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

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

Да, это можно сделать одним из способов. Но на самом деле все возможно.

person Michael Borgwardt    schedule 02.11.2010
comment
Привет, Майкл, так что это зависит от требований? Нет жесткого и быстрого правила, согласно которому фабричные методы всегда должны проверять наличие уже существующего экземпляра. - person t0mcat; 02.11.2010
comment
@t3ch: Да, абсолютно. Дело в том, что вы можете сделать это с помощью фабричных методов, если это полезно и что вы хотите сделать... у вас нет такой возможности с new. - person ColinD; 02.11.2010
comment
Вау, спасибо. По крайней мере, теперь я знаю, где это могло бы пригодиться. С помощью этого подхода можно лучше понять синглтоны. - person t0mcat; 02.11.2010

Во-первых, спасибо за ваш выбор в Java-lit: книга Блоха — отличный учебник для начинающих.

Чтобы ответить на ваш второй вопрос («в отличие от конструкторов, статические фабричные методы не обязаны создавать новый объект каждый раз, когда они вызываются»), важно понимать, что здесь Блох говорит, что со статической фабрикой у вас есть вариант: возврат нового объекта или возврат ранее существовавшего. Все зависит от того, что вы хотите сделать.

Например, предположим, что у вас есть действительно простой класс значений типа Money. Ваш статический фабричный метод, вероятно, должен возвращать новый экземпляр, то есть новый объект с определенным значением Money. Ну вот так:

public class Money { 

    private Money(String amount) { ... } /* Note the 'private'-constructor */

    public static Money newInstance(String amount) {
        return new Money(amount);
    }

}

Но допустим, у вас есть какой-то объект, который управляет каким-то ресурсом, и вы хотите синхронизировать доступ к этому ресурсу через какой-то класс ResourceManager. В этом случае вы, вероятно, захотите, чтобы ваш статический фабричный метод возвращал всем один и тот же экземпляр самого себя, заставляя всех проходить через один и тот же экземпляр, чтобы этот 1 экземпляр мог контролировать процесс. Это соответствует шаблону singleton. Что-то вроде этого:

public ResourceManager {

    private final static ResourceManager me = new ResourceManager();

    private ResourceManager() { ... } /* Note the 'private'-constructor */

    public static ResourceManager getSingleton() {
        return ResourceManager.me;
    }
}

Приведенный выше метод заставляет вашего пользователя иметь возможность использовать только один экземпляр, позволяя вам точно контролировать, кто (и когда) имеет доступ к тому, чем вы управляете.


Чтобы ответить на ваш первый вопрос, рассмотрите это (правда, не лучший пример, это довольно нестандартно):

public class Money {

    private Money(String amount) { ... }


    public static Money getLocalizedMoney( MoneyType localizedMoneyType, String amount ) { 
        switch( localizedMoneyType ) {
            case MoneyType.US:
                return new Money_US( amount );
            case MoneyType.BR:
                return new Money_BR( amount );
            default:
                return new Money_US( amount );
        }
    }
}

public class Money_US extends Money { ... }

public class Money_BR extends Money { ... }

Обратите внимание, как я теперь могу это сделать:

Money money = Money.getLocalizedMoney( user_selected_money_type );
saveLocalizedMoney( money );

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

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

person Bane    schedule 02.11.2010
comment
› просто подумайте, что новичку иногда полезно увидеть реальный код. Спасибо за внимание к этому факту, Бэйн. Ваши примеры действительно полезны, особенно тот, где вы создаете частный экземпляр и каждый раз возвращаете один и тот же экземпляр для ResourceManager. - person t0mcat; 02.11.2010
comment
np — всякий раз, когда я изучаю новую концепцию, я всегда борюсь с абстрактными/расплывчатыми ответами — жесткий код в сочетании с кратким объяснением обычно дает мне гораздо больше. Кстати, пункт 3 в книге Блоха даст вам гораздо больше информации о подходе singleton-factory. - person Bane; 02.11.2010
comment
Спасибо Бэйн. с нетерпением жду этого. Вы, ребята, увидите кучу вопросов от меня :) - person t0mcat; 02.11.2010
comment
В качестве примера, который вы привели выше, почему нам не нужно использовать статический фабричный метод вместо общедоступного конструктора для первого примера? Мы можем сделать его общедоступным конструктором, а затем использовать его, я действительно не знаю, почему мы делаем конструктор закрытым и создаем статический фабричный метод, чтобы делать то же самое с общедоступным конструктором? - person Shen; 28.11.2018
comment
@Shen ответ слишком длинный для ответа здесь; сам автор перечисляет 5 причин, и объяснение его рассуждений занимает около 4 полных страниц. См. «Эффективная Java» Джошуа Блоха, пункт 1: рассмотрите статические фабричные методы вместо конструкторов. Короткий ответ, однако, заключается в том, что вам не обязательно делать это строго, но использование статических фабричных методов дает вам гораздо больше гибкости. Теперь, обращаясь конкретно к вашему вопросу о первом примере, этот пример немного надуманный и упрощенный; не обязательно самый ясный, чтобы передать почему. - person Bane; 30.11.2018
comment
@ Тем не менее, это все еще можно рассматривать как основу для будущих улучшений. Если вы просто пишете с помощью конструктора, вы можете быть привязаны к этому шаблону позже, когда захотите ввести код типа локализации денег, который я показал после первого примера. Если, с другой стороны, вы по умолчанию скрываете конструктор, то ваш упрощенный фабричный шаблон можно использовать для вызова getLocalizedMoneyType(DefaultMoneyTypeBasedOnLocale, amount) -- и уже существующий клиентский код не прерывается, потому что никто уже не делал new Money(amount). - person Bane; 30.11.2018
comment
@Bane спасибо за ваш ответ, основываясь на вашем ответе, я знаю, что у нас есть много случаев использования статического фабричного метода вместо конструктора, в first-example случае мы используем статический фабричный метод, потому что мы хотим использовать другой тип и улучшение в будущем, не так ли? - person Shen; 05.12.2018
comment
@ Шен прав. Для простейших классов значений это, вероятно, излишне. Но для всего, что вы предполагаете, может быть немного более сложное использование (подумайте, например, другие, использующие этот код в стиле API; обратите внимание, не обязательно REST-API; просто API общего назначения), тогда да, это то, что я говорю : что мы должны использовать статические фабричные методы, чтобы абстрагироваться от будущих изменений конечного пользователя в базовой реализации и оставить нам, разработчикам, более гибкий дизайн. - person Bane; 10.12.2018

Когда вы используете ключевое слово new, вы, как разработчик, знаете, что JDK создаст новый экземпляр этого объекта. Что говорит автор, когда вы используете статический метод, разработчик больше не знает, создает ли метод новый экземпляр или, возможно, делает что-то еще. Что-то еще может быть, повторное использование кэшированных данных, объединение объектов, создание частной реализации и возврат подкласса класса.

person Amir Raminfar    schedule 02.11.2010
comment
›› если метод создает новый экземпляр или, возможно, делает что-то еще. Амир, если статический метод не создает новый экземпляр или не возвращает новый объект, то зачем нам нужен статический фабричный метод? - person t0mcat; 02.11.2010
comment
Вам не всегда может понадобиться новый экземпляр чего-то. Возьмем, к примеру, соединения с базой данных: вы делаете это в java jdbc.newConnection(). Но java не создает новый экземпляр каждый раз. Сначала он проверяет, существует ли уже соединение. Если нет, он получит новый экземпляр. Другим примером может быть то, что вы хотите создать синглтон. (Синглтоны имеют свою собственную проблему). Это означает, что хорошо читается, почему должен быть только один экземпляр класса. Итак, вы снова сделаете свой конструктор закрытым и разрешите использовать свой API только через статические классы. - person Amir Raminfar; 02.11.2010
comment
Кроме того, я не говорю, что статические фабричные методы в любом случае хороши! Я просто говорю, что иногда вам это нужно, но используйте его только в случае необходимости. Использование конструкторов должно быть чище, чем какой-то запутанный метод, который разработчик должен искать. - person Amir Raminfar; 02.11.2010
comment
@t3ch: Integer.valueOf(int) также является хорошим примером статического фабричного метода, который не всегда создает новый объект. Новые JVM обычно кэшируют объекты Integer для настраиваемого диапазона (я думаю, по умолчанию от -128 до 127). Это делает бокс гораздо более эффективным. - person Mark Peters; 02.11.2010
comment
Спасибо, Марк. Так это относится ко всем классам-оболочкам? например. Boolean.valueOf(логическое значение b)?? - person t0mcat; 02.11.2010
comment
@Amir: Я не согласен с тем, что статические фабричные методы следует использовать только в случае необходимости. Как описано в разделе «Эффективная Java», они имеют многочисленные преимущества перед конструкторами, и их так же легко обнаружить, как и конструкторы... просто введите ClassName., и любая IDE выдаст список доступных вам статических методов. - person ColinD; 02.11.2010
comment
@t3ch: Не все. Double, например, очень сложно кэшировать, потому что он имеет точность с плавающей запятой. Логическое значение кэширует оба значения (в Boolean.TRUE и Boolean.FALSE). Беглый взгляд на другие показывает, что Shorts и Longs кэшируются от -128 до 127, но этот диапазон не настраивается, точно так же кэшируются все объекты Byte. - person Mark Peters; 02.11.2010
comment
@ColinD: я думаю, что статические классы имеют свое место. В книге также рассказывается о недостатках, которые могут быть важны для вас. Я думаю, что когда вам нужно контролировать создание объектов, вам подойдут статические методы. Однако, если бы каждый класс в каком-то API имел newInstance(), то это было бы не очень похоже на Java. PS. Не все используют IDE;) - person Amir Raminfar; 02.11.2010
comment
@Amir: Конструкторы должны быть чище, чем какой-то запутанный метод, который разработчик должен искать... об этом позаботятся согласованные соглашения об именах. Гораздо проще просматривать имена статических методов, чем пытаться выяснить, какой из 100 конструкторов вы хотите использовать, основываясь на их аргументах. Слишком легко ошибиться с конструкторами (о, я хотел new SomeType( int, int, String, int), а не new SomeType(int, int, int, String)...) - person Mark Peters; 02.11.2010
comment
@t3ch: это кэширование, например. Integer#valueOf(int) между прочим называется шаблон легковеса. - person BalusC; 02.11.2010
comment
@BalusC: Как так? Это не похоже на то, что разные экземпляры обмениваются данными. - person Mark Peters; 02.11.2010
comment
Вот цитата из книги: Второй недостаток статических фабричных методов заключается в том, что их трудно отличить от других статических методов. Они не выделяются в документации API так, как это делают конструкторы, поэтому может быть сложно понять, как создать экземпляр класса, который предоставляет статические фабричные методы вместо конструкторов. К этому моменту, я снова думаю, если вы должны использовать их для поддержания чистоты, сделайте это, но не создавайте общедоступную статическую фабрику для каждого отдельного класса, который вы пишете! - person Amir Raminfar; 02.11.2010
comment
@Amir: Этот недостаток просто присутствует, так что аргумент кажется сбалансированным. Использование аннотаций и правильного именования может легко дать вам статическую поддержку, необходимую для того, чтобы вы могли различать их в Javadocs и т. Д. Просто не веская причина не использовать их. - person Mark Peters; 02.11.2010
comment
@Марк: я тебя не понимаю. Эти экземпляры в любом случае неизменяемы. - person BalusC; 02.11.2010
comment
@Mark: То есть вы говорите, что для каждого класса, который вы пишете, вы также создаете статический метод? Даже если это просто класс с одним или двумя параметрами? Не удалось также указать те же самые правильные имена, которые вы предлагаете для конструктора. - person Amir Raminfar; 02.11.2010
comment
@BalusC: я понимаю, к чему вы пришли, но кэширование само по себе не является примером шаблона легковеса. Было бы легковесом, если бы какой-то класс имел сотни экземпляров, ссылающихся на одно и то же целое число. - person Mark Peters; 02.11.2010
comment
@Amir: вы не можете выбрать имя конструктора в Java. И, конечно, я не делаю это для каждого класса, но уж точно не из-за отсутствия ясности. Есть и другие веские причины (например, это в принципе невозможно, если тип предназначен для расширения). - person Mark Peters; 02.11.2010
comment
@Amir: Нет, конечно, не для каждого класса. Конструкторы хороши, если не лучше, для многих классов... особенно сервисы для использования с внедрением зависимостей и тому подобное. Я все еще думаю, что их следует использовать чаще, чем кажется, что вы подразумеваете, что они должны. - person ColinD; 02.11.2010
comment
@Amir: Я с Марком в этом вопросе - солидно. Статические фабричные методы — это наиболее подходящий способ. При этом, если вы пишете простой класс значений, предназначенный для использования вами и только вами, то (мое практическое правило) я не беспокоюсь о статическом фабричном классе - я просто использую конструктор. Но большую часть книги Блоха следует понимать в контексте РАЗРАБОТЧИКА API. То есть, если вы разрабатываете API, предназначенный для использования кем-то другим, да, вы почти всегда должны использовать метод статической фабрики. - person Bane; 02.11.2010
comment
@Mark, я согласен, что в некоторых случаях использование статического метода должно быть чище. Например, сказать, что newInstanceFromFoo(Foo foo) намного легче читать, чем 4 конструктора с разными параметрами. Я просто думал, что вы говорите, что это всегда так, а это не так. Больше всего я ненавижу эти разговоры за то, что некоторые люди воспринимают их как черное и белое. На самом деле это зависит от проблемы, которую вы пытаетесь решить. Вроде кэширование байтов имеет смысл. :) - person Amir Raminfar; 02.11.2010
comment
Ребята, как лучше всего реализовать статическую фабрику? Каждый класс должен определить его? (где бы вы ни чувствовали необходимость). Или создайте один общий статический класс и каким-то образом поместите туда логику для всех необходимых статических методов, используемых вашим приложением. - person t0mcat; 02.11.2010
comment
@Bane, Хм, я не согласен. Создание API или нет не имеет к этому никакого отношения. Какую проблему вы пытаетесь решить. Например, класс Intent в java android api. API могли быть только статическими методами для Intent. Но они мудро выбрали конструкторы и статические методы, потому что иногда это просто не имеет смысла. - person Amir Raminfar; 02.11.2010
comment
@ t3ch Если у вас есть один общий класс, то он называется фабрикой. Вы можете создать фабрику, если все классы связаны друг с другом. Я думаю, что лучше иметь относительные статические методы в самом классе. Опять же, иногда вы хотите разделить фабрику и класс, который в этом случае вы бы создали фабрику. Не существует одного решения, которое решит все. Просто посмотрите, какое лучшее решение для вашей проблемы. - person Amir Raminfar; 02.11.2010
comment
@t3ch: Как правило, вы помещаете статические фабричные методы, которые возвращают экземпляры данного класса в этот класс. Однако это не всегда возможно, особенно если это интерфейс или класс, который вы не можете контролировать, и который должны возвращать фабричные методы. Классы Predicates, Functions и Lists в Guava являются примерами коллекций статических фабричных методов в другом классе. - person ColinD; 02.11.2010
comment
@Amir: тема «статического фабричного метода» действительно применима за пределами создания API. Но если вы сделаете шаг назад и проанализируете работу Блоха в целом, я думаю, что выделяется одна вещь, общая для всей работы: API. Многие из его правил неприменимы или менее применимы при разработке собственного кода, используемого в частном порядке, но если рассматривать их в контексте API, они имеют гораздо больше смысла. - person Bane; 02.11.2010
comment
@Amir: поскольку я не работаю с Android, я не могу комментировать класс Intent; конечно, бывают случаи, когда простой конструктор работает просто отлично, но я придерживаюсь утверждения, что при создании API статические фабричные методы в большинстве случаев будут служить вам лучше. Почему??? Во-первых, вы можете предоставить явные соглашения об именовании и можете возвращать варианты того, что вы создали — вы можете изменить базовую реализацию так, как хотите, при условии, что конечный результат тот же. С чистыми конструкторами вы ограничиваете себя. - person Bane; 02.11.2010
comment
@Бейн, я согласен с тобой. Если вам нужно контролировать то, что строится, то это правильно. Думаю, я никогда не думал об эффективной Java только для API. Я всегда думал, что это то, о чем должен думать хороший программист, независимо от того, где будет находиться код. Моя точка зрения, которую я пытался сделать, заключается в том, что иногда вы хотите, чтобы все было просто. Но мы склонны все усложнять. Иногда вам приходится использовать статические методы, потому что у вас нет более чистой модели. Я просто беру каждую проблему и думаю о правильных инструментах для ее решения. - person Amir Raminfar; 02.11.2010
comment
@Amir: Я не думаю, что вы ошибаетесь, говоря, что иногда конструкторы - более простой подход. И чем проще, тем лучше. Что касается EJ - когда я впервые прочитал его в школе, часто велись жаркие споры о правилах Блоха; казалось, слишком много исключений из его правил — как только я заметил, что его правила, как правило, работают намного лучше, если рассматривать их в контексте API, все согласились, что они имеют гораздо больше смысла — в конце концов, Блох — парень, занимающийся API. . Слишком многие из его правил не применимы во всех обстоятельствах — я имею в виду, просто подумайте об одной этой теме. ;) - person Bane; 02.11.2010
comment
@Amir: только что наткнулся на реальный пример, который напомнил мне о другом хорошем использовании методов статической фабрики; думал поделюсь. Напишите класс, который расширяет DefaultMutableTreeNode, принимая String в свой конструктор — используйте эту String для предоставления заголовка вашего узла, вызывая super(stringArg). Теперь попробуйте проверить, что строка не является нулевой/пустой, прежде чем создавать объект, бросьте NPE, если это так. Конечно, вы не можете этого сделать, так как super() должна быть первой строкой в ​​конструкции. Но вы можете добиться этого с помощью статического факта. метод. - person Bane; 05.11.2010
comment
Да, я согласен. Собственно, я и сам думал об этом прошлой ночью. Я думаю, что я подумал, что будет лучшим способом объяснить, что я не думаю, что вы должны помечать свои конструкторы как частные. Пользователь должен иметь возможность использовать статические помощники или конструкторы. Итак, пока есть конструктор и у вас есть дополнительные вспомогательные функции, его чистый дизайн. Конечно, если вам нужно контролировать количество экземпляров, у вас нет другого выбора, кроме как сделать его приватным. - person Amir Raminfar; 05.11.2010

класс объекта, возвращаемый статическим фабричным методом, не является общедоступным

Часто статический фабричный метод возвращает либо объект, типизированный как интерфейс (чаще всего), либо иногда некоторый базовый класс (реже). В любом случае вы не знаете точного класса возвращаемого объекта.

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

в отличие от конструкторов статические фабричные методы не обязаны создавать новый объект каждый раз, когда они вызываются

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

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

person TreyE    schedule 02.11.2010
comment
Привет, Трей. Ваш пример синглтона действительно развеял мои сомнения. Спасибо :) - person t0mcat; 02.11.2010

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

Обратите внимание, что фабричный метод без аргументов of() всегда возвращает один и тот же экземпляр (он не создает каждый раз новый экземпляр). Если вы посмотрите внимательно, вы также заметите, что его фабричный метод copyOf(Iterable) на самом деле возвращает объект, который ему передается, если этот объект сам является ImmutableList. Оба они используют тот факт, что ImmutableList гарантированно никогда не изменится.

Обратите также внимание на то, как различные фабричные методы в нем возвращают разные подклассы, такие как EmptyImmutableList, SingletonImmutableList и RegularImmutableList, не раскрывая типы этих объектов. Сигнатуры методов просто показывают, что они возвращают ImmutableList, а все подклассы ImmutableList имеют доступ к пакету (по умолчанию), что делает их невидимыми для пользователей библиотеки. Это дает все преимущества нескольких классов реализации, не добавляя сложности с точки зрения пользователя, поскольку им разрешено просматривать только ImmutableList как один тип.

Помимо ImmutableList, в большинстве классов с возможностью создания экземпляров в Guava используются статические фабричные методы. Гуава также иллюстрирует многие принципы, изложенные в книге «Эффективная Java» (неудивительно, учитывая, что она была разработана на основе этих принципов и под руководством самого Джоша Блоха), так что вам может быть полезно взглянуть на нее поближе, когда вы повторная работа над книгой.

person ColinD    schedule 02.11.2010
comment
Спасибо за пример, Колин. Прохождение через это. - person t0mcat; 02.11.2010