Обзор
Традиционный способ получить экземпляр класса - это использовать публичный конструктор.
```javascript
public class Member {
}
public enum MemberStatus {
}
Обычно публичного конструктора достаточно, но часто бывает целесообразно предоставить статический фабричный метод (static factory method) в дополнение к конструктору, чтобы упростить создание экземпляров желаемым способом для пользователя.
Типичным примером статического фабричного метода является метод Boolean.valueOf().
```javascript
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
Этот метод принимает значение boolean в качестве входных данных и возвращает объект Boolean.
Преимущества статических фабричных методов
Они могут иметь имена.
Параметры конструктора и сам конструктор не всегда достаточно точно описывают характеристики возвращаемого объекта. Например, трудно понять, какими характеристиками обладает Member, просто взглянув на его основной конструктор (name, age, hobby, memberStatus).
Кроме того, one signature может быть использован для создания одного конструктора, в то время как статическому фабричному методу можно присвоить имя, что позволяет создавать несколько статических фабричных методов с одной сигнатурой для возврата экземпляров.
```javascript
public class Member {
}
Как показано выше, вместо того, чтобы разделять членство по статусу с помощью конструктора, создание нескольких статических фабричных методов с одинаковой сигнатурой позволяет пользователям создавать экземпляры Member с определенными навыками без каких-либо недоразумений.
Посмотрим на библиотеку, определенную JDK: существует статический фабричный метод BigInteger, probablePrime().
```javascript
public static BigInteger probablePrime(int bitLength, Random rnd) {
if (bitLength < 2)
throw new ArithmeticException("bitLength < 2");
}
Сравнивая обычный конструктор BigInteger и его статический фабричный метод, probablePrime(), можно сказать, что последний, безусловно, сделает его более ясным.
Им не нужно создавать новый экземпляр каждый раз, когда их вызывают.
```javascript
public static Boolean valueOf(boolean b) {
return (b ? Boolean.TRUE : Boolean.FALSE);
}
Статический метод Boolean.valueOf() предварительно кэширует экземпляры и возвращает их. Эта функция может значительно улучшить производительность в ситуациях, когда часто запрашивается дорогостоящий объект, и,шаблон Flyweightможет быть рассмотрен как подобная техника.
Классы, использующие статический фабричный метод, возвращающий один и тот же объект для повторяющихся запросов, могут контролировать жизненный цикл объектов; таким образом, их можно назвать классами управления экземплярами. Управление экземплярами позволяет создавать классы-синглтоны и классы, которые невозможно инстанцировать. Кроме того, он может гарантировать, что в классе с неизменяемым значением существует только один экземпляр.
Управление экземплярами является основой шаблона Flyweight, а перечисления гарантируют, что будет создан только один экземпляр.
Предположим, что вам нужно посадить дерево в Minecraft. Если каждый объект дерева создается заново, может произойти переполнение памяти.
Поэтому, как показано выше, объекты красного и зеленого дерева могут быть сохранены, а только их положения изменены. Конечно, количество цветов может увеличиться, поэтому сохранение деревьев в такой структуре данных, как Map, может быть эффективным.
```javascript
public class Tree {
}
public class TreeFactory {
// Используйте HashMap для управления созданными деревьями.
public static final Map<String, Tree> treeMap = new HashMap<>();
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
}
Разница с шаблоном синглтона
Шаблон синглтон позволяет создать только одно дерево в классе Tree. Таким образом, если бы использовался шаблон синглтон, пришлось бы изменять цвет этого единственного объекта. То есть шаблон синглтон позволяет иметь только один экземпляр, независимо от типа.
Шаблон Flyweight используется в пуле постоянных строк Java.
Они могут возвращать объекты подтипов возвращаемого типа.
Вы можете понять это преимущество, если когда-либо использовали метод asList() из утилиты Arrays.
```javascript
public static List asList(T... a) [
return new ArrayList<>(a);
}
Он оборачивает значение в ArrayList, который является подтипом List, и возвращает его, но пользователю не нужно знать об этой реализации. То есть у вас есть гибкость в выборе класса возвращаемого объекта, что позволяет разработчикам не раскрывать реализацию и сохранять API небольшими.
О статических методах в интерфейсах Java
До Java 8 интерфейсам нельзя было объявлять статические методы, поэтому, если вам нужен статический метод, возвращающий интерфейс "Type", необходимо создать сопутствующий класс "Types", который является неинстанцируемым, и определить в нем метод.
Типичным примером является JCF, в котором есть 45 утилитарных реализаций, большинство из которых предоставляют доступ к своим экземплярам только через один сопутствующий класс, java.util.Collections. В частности, среди этих реализаций есть некоторые, которые являются public, и, таким образом, создавать экземпляры можно только с помощью статических фабричных методов. (Разумеется, эти реализации также не наследуются.)
Кроме того, 45 реализаций не являются открытыми, поэтому API может быть значительно меньше.
```javascript
// Пример интерфейса и сопутствующего класса
List empty = Collections.emptyList();
Однако с Java 8 интерфейсам можно напрямую добавлять статические методы, поэтому нет необходимости определять сопутствующий класс.
Они могут возвращать разные объекты подклассов в зависимости от входных параметров.
Помимо простого возврата подтипов, вы можете вернуть разные подтипы в зависимости от значения параметра. Например, если вы хотите вернуть different MemberStatus в зависимости от балла, вы можете сделать это, создав статический фабричный метод, как показано ниже, и реализовав логику сравнения внутри него.
```javascript
public enum MemberStatus {
}
@DisplayName("Тест MemberStatus")
class MemberStatusTest {
}
Статические фабричные методы могут быть созданы до того, как будет существовать класс возвращаемого объекта.
В предложениикласс возвращаемого объектаозначает файл класса, который мы создаем.
Обратите внимание, что Class> представляет объект Class, который загрузчик классов назначает объекту Class в куче при загрузке класса. Этот объект Class содержит различные метаданные о нашем классе.
```javascript
package algorithm.dataStructure;
public abstract class StaticFactoryMethodType {
}
В приведенном выше коде, можно использовать reflection tech для создания объекта Class из класса реализации путем инициализации. При этом статический фабричный методне должен существовать, когда создается
Если в пути algorithm.dataStructure.StaticFactoryMethodTypeChild нет реализации во время использования статического фабричного метода, будет выброшена ошибка. Однако при создании статического фабричного метода нет проблем, поэтому говорят, что он гибок.
```javascript
public interface Test {
}
public class Main {
}
Без использования отражения можно добиться такой же гибкости. В статическом фабричном методе Test.create() нет проблем с созданием, даже если нет реализации. Конечно, NPE будет выброшено во время фактического использования, поэтому вам нужно вернуть реализацию позже.
Эта гибкость является основой для создания фреймворков для поставщиков услуг. Типичным примером является JDBC. JDBC Фреймворк поставщика услуг предоставляет реализации сервисов, а роль фреймворка заключается в том, чтобы управлять этими реализациями и скрывать клиента от реализаций. (DIP)
- Компоненты фреймворка поставщика услуг
- Сервисный интерфейс
- Определяет поведение реализации
- Connection в JDBC
- API регистрации поставщика
- Поставщик регистрирует реализацию
- DriverManager.registerDriver() в JDBC
- API доступа к сервису
- Клиент использует его для получения экземпляра сервиса; если условие не указано, возвращается реализация по умолчанию или поддерживаемые реализации поочередно.
- Соответствует статическому фабричному методу
- DriverManager.getConnection() в JDBC
- (Необязательно) Интерфейс поставщика услуг
- Без него вам потребуется использовать отражение при создании экземпляров каждой реализации
- JDBC's Driver
Шаблон фреймворка для поставщиков услуг имеет много вариаций, включая шаблон моста, фреймворки внедрения зависимостей и т.д.
```javascript
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connection = null;
connection = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:ORA92", "root", "root");
// Логика sql с использованием различных утверждений
Обычно JDBC пишется как показано выше. С помощью Class.forName() регистрируется OracleDriver, который является одним из реализаций Driver, а затем с помощью DriverManager.getConnection() получается OracleDriver из реализаций Connection.
Здесь мы можем видеть, что Connection является интерфейсом сервиса, DriverManager.getConnection() является API доступа к сервису, а Driver является интерфейсом поставщика услуг. Однако API регистрации поставщика DriverManager.registerDriver() не используется. Тем не менее, мы все равно можем зарегистрировать OracleDriver, реализацию Driver, только с помощью Class.forName(). Как это возможно?
Принцип работы Class.forName()
Этот метод принимает имя физического файла класса в качестве аргумента и просит JVM загрузить этот класс. Затем загрузчик классов сохраняет метаданные класса в области методов и назначает объект Class области кучи. Кроме того, завершается загрузкастатические поля и статические блоки инициализируются, и именно в это время используется API регистрации поставщика.
```javascript
public class OracleDriver implements Driver {
}
На самом деле, если вы посмотрите на OracleDriver, вы обнаружите, что в статическом блоке DriverManager.registerDriver() используется для регистрации OracleDriver, который является реализацией Driver.
Анализатор класса DriverManager
```javascript
public class DriverManager {
}
Хотя класс DriverManager на самом деле much more сложнее, вот упрощенная версия для более легкого понимания. Как упоминалось ранее, DriverManager.registerDriver() вызывается из статического блока OracleDriver для регистрации OracleDriver, и вы можете легко получить реализацию Connection с помощью getConnection().
Если присмотреться к API доступа к пользователям, getConnection(), можно увидеть, что он получает Connection из интерфейса Driver. Если бы не было интерфейса поставщика услуг Driver, вам пришлось бы использовать отражение, подобное Class.forName(), для возврата желаемой реализации Connection. При этом реализация Connection не должна существовать во время создания статического фабричного метода.
Вместо этого вы используете интерфейс Driver, динамически регистрируете реализацию Driver, а затем легко получаете соответствующую реализацию Connection.
Обратите внимание, что я проанализировал фактический код JDK для метода getConnection() класса DriverManager, но вы можете пропустить его, если он вас не интересует.
```javascript
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
}
Во-первых, вызывается public статический метод getConnection(), и url, Properties и CallerClass передаются в качестве аргументов для private статического метода getConnection(). При этом Reflection.getCallerClass() получает класс, который вызвал этот public статический метод getConnection(). Например, если класс Car вызвал getConnection(), Reflection.getCallerClass() получит объект Class для Car.
```javascript
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
}
callerCL - это объект загрузчика классов, который создается либо из загрузчика классов caller, либо из загрузчика классов текущего потока. Затем он перебирает список зарегистрированных драйверов registeredDrivers, по одному вытаскивая aDriver. Затем, если этот Driver разрешен с помощью isDriverAllowed(), он пытается получить объект Connection с помощью этого Driver и возвращает его. isDriverAllowed() проверяет, существует ли aDriver в caller.
Преимущества фреймворка JDBC
Суть фреймворка JDBC заключается в том, что интерфейс Driver, интерфейс Connection и их фактические реализации полностью разделены и предоставлены. Поскольку фреймворк создает шаблон с использованием интерфейсов, и различные классы реализации могут быть созданы в соответствии с этим шаблоном, он очень гибкий.
Таким образом, даже если появится другая СУБД, ее поставщик может предоставить Driver и интерфейс Connection, чтобы разработчики, использующие Java, могли использовать тот же API драйвера, что и другие драйверы СУБД.
Недостатки статических фабричных методов
Поскольку им требуются public или protected конструкторы для наследования, невозможно создать подкласс, если предоставляется только статический фабричный метод.
Однако это ограничение может быть преимуществом, поскольку оно поощряет композицию, а не наследование, и это ограничение необходимо соблюдать, чтобы создать неизменяемый тип.
Статические фабричные методы трудно найти для программистов.
Поскольку они не включены явно в описания API, как конструкторы, разработчики должны хорошо документировать API и придерживаться общепринятых соглашений об именовании.
Схемы именования статических фабричных методов
- from
- Принимает один параметр и возвращает экземпляр этого типа.
- Date date = Date.from(instant);
- of
- Принимает несколько параметров и возвращает экземпляр соответствующего типа.
- Set faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
- Более подробная версия from и of.
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- instance 혹은 getInstance
- Возвращает экземпляр, указанный в качестве параметра, но не гарантирует, что это тот же экземпляр.
- StackWalker luke = StackWalker.getInstance(options);
- create 혹은 newInstance
- То же, что и instance 혹은 getInstance, но гарантирует, что он всегда создает новый экземпляр.
- Object newArray = Array.newInstance(classObject, arraylen);
- getType
- То же, что и getInstance, но используется, когда статический фабричный метод определяется в другом классе, чем возвращаемый.
- FileStore fs = Files.getFileStore(path);
- newType
- То же, что и newInstance, но используется, когда статический фабричный метод определяется в другом классе, чем возвращаемый.
- BufferedReader br = Files.newBufferedReader(path);
- type
- Сокращенная версия getType 혹은 newType.
- List litany = Collections.list(legacyLitany);
Сводка
Статические фабричные методы и публичные конструкторы имеют разные варианты использования, поэтому используйте их соответствующим образом.
Источник