Шаблон фабрики — это шаблон творческого проектирования, целью которого является предоставление интерфейса для создания семейств связанных или зависимых объектов без указания их конкретных классов. Логика создания инкапсулирована в фабрике, которая либо предоставляет метод для ее создания, либо делегирует создание объекта подклассу. Клиент не знает о различных реализациях интерфейса или класса. Клиенту нужно только знать фабрику, которую можно использовать для получения экземпляра одной из реализаций интерфейса. Клиенты отделены от создания объектов.
Часто фабричный шаблон реализуется как одноэлементный или статический класс, поскольку требуется только один экземпляр фабрики. Это централизует создание объекта.
CDI Framework
В Java EE мы можем использовать преимущества инфраструктуры CDI для создания объектов, не зная деталей их создания. Разделение происходит в результате реализации Java EE инверсии управления. Самое важное преимущество, которое это дает, — это отделение классов более высокого уровня от классов более низкого уровня. Такое разделение позволяет изменять реализацию конкретного класса, не влияя на клиента: уменьшая связь и повышая гибкость.
Сама структура CDI является реализацией фабричного шаблона. Контейнер создает квалифицирующий объект во время запуска приложения и внедряет его в любую точку инъекции, которая соответствует критерию инъекции. Клиенту не нужно ничего знать о конкретной реализации объекта, даже имя конкретного класса не известно клиенту.
1
2
3
|
public class CoffeeMachine implements DrinksMachine { // Implementation code } |
Используйте это так:
1
2
|
@Inject DrinksMachine drinksMachine; |
Здесь контейнер создает экземпляр конкретного класса CoffeeMachine , он выбирается на основе его интерфейса DrinksMachine и внедряется везде, где контейнер находит подходящую точку внедрения. Это самый простой способ использовать реализацию фабричного шаблона CDI. Однако это не самое гибкое.
Disambiguation
Что произойдет, если у нас будет более одной конкретной реализации интерфейса DrinksMachine ?
1
2
3
4
5
6
7
|
public class CoffeeMachine implements DrinksMachine { // Implementation code } public class SoftDrinksMachine implements DrinksMachine { // Implementation code } |
Какую реализацию следует вводить? SoftDrinksMachine или CoffeeMachine ?
1
2
|
@Inject DrinksMachine drinksMachine; |
Контейнер не знает, и поэтому развертывание завершится с ошибкой «неоднозначных зависимостей».
Отборочные
Так как же контейнер различает конкретные реализации? Java EE дает нам новый инструмент: квалификаторы. Классификаторы — это пользовательские аннотации, которые отмечают конкретный класс и точку, в которую контейнер должен внедрить объект.
Возвращаясь к нашей машине Drinks и двум конкретным классам одного и того же типа CoffeeMachine и SoftDrinksMachine, мы бы различали их с помощью двух квалификационных аннотаций:
1
2
3
4
|
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.METHOD, ElementType.FIELD}) public @interface SoftDrink |
1
2
3
4
|
@Qualifier @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.METHOD, ElementType.FIELD}) public @interface Coffee |
Мы создаем один классификатор с именем SoftDrink . Это аннотирует конкретный класс SoftDrinksMachine, а Coffee аннотирует класс CoffeeMachine .
Аннотация @Target ограничивает, где мы можем использовать эти квалификаторы для маркировки точек внедрения, в данном случае для точек введения метода и поля. Аннотация с политикой хранения RUNTIME гарантирует, что примечание будет доступно JVM во время выполнения.
Возможные значения для Target: TYPE, METHOD, FIELD, PARAMETER.
Две конкретные реализации интерфейса DrinksMachine аннотированы соответствующим образом. Класс CoffeeMachine аннотирован @Coffee, а класс SoftDrinksMachine — @SoftDrink .
1
2
3
4
|
@Coffee public class CoffeeMachine implements DrinksMachine { // Implementation code } |
1
2
3
4
|
@SoftDrink public class SoftDrinksMachine implements DrinksMachine { // Implementation code } |
Теперь вы комментируете точки впрыска. Используйте квалификатор @SoftDrink, чтобы указать, куда вы хотите, чтобы контейнер вводил класс SoftDrinksMachine, и квалификатор @Coffee, где вы хотите, чтобы контейнер вводил CoffeeDrinkMachine . Теперь мы дали понять контейнеру, куда должны быть внедрены наши конкретные реализации, и развертывание будет успешным.
1
2
|
@Inject @SoftDrink DrinksMachine softDrinksMachine; |
1
2
|
@Inject @Coffee DrinksMachine coffeeDrinksMachine; |
Мы видели, как среда CDI в Java EE является реализацией фабричного шаблона, как она скрывает конкретную реализацию объекта и позволяет отделить создание от его использования. Мы видели, как используются квалификаторы для выбора требуемой реализации без необходимости что-либо знать о создании объектов.
Важно помнить, что инфраструктура CDI будет создавать только экземпляры POJO, которые удовлетворяют всем условиям спецификации управляемых компонентов JSR 299. Но что, если объект, который вы хотите внедрить, не делает, это означает, что мы не можем использовать преимущества CDI возможности внедрения фреймворка для классов, которые не соответствуют. Нет, это не так. Java EE предоставляет нам решение. Давайте погрузимся глубже и посмотрим, как мы можем использовать платформу CDI для внедрения ЛЮБОГО класса ЛЮБОГО типа в точку внедрения.
Методы продюсера
Java EE имеет функцию, называемую методами производителя. Эти методы обеспечивают способ создания экземпляров и, следовательно, делают их доступными для объектов внедрения, которые не соответствуют спецификациям управляемого компонента, таких как объекты, которым для правильной реализации требуется параметр конструктора. Объекты, значение которых может измениться во время выполнения, и объекты, создание которых требует некоторой настраиваемой инициализации, также могут быть подготовлены для внедрения с помощью метода производителя.
Давайте посмотрим на метод продюсера, который создает список, заполненный объектами Books.
1
2
3
4
5
6
|
@Produces @Library public List<Book> getLibrary(){ // Generate a List of books called 'library' return library; } |
Список объектов Book будет введен в аннотированную точку @Library.
Используйте это так:
1
2
|
@Inject @Library List<Books> library; |
Важной особенностью метода производителя является его область применения. Это будет определять, когда метод вызывается и как долго будет жить объект, который он производит.
По умолчанию область действия метода производителя — @DependentScoped . Это означает, что он наследует область своего клиента.
Мы можем расширить этот пример, расширив его возможности. Если мы аннотируем метод источника @RequestScoped, он будет вызываться только один раз для каждого HTTP-запроса, в котором он участвует, и будет действовать в течение всего срока действия запроса.
1
2
3
4
5
6
7
|
@RequestScoped @Produces @Library public List<Book> getLibrary(){ // Generate a List of books called 'library' return library; } |
Возможные области применения:
- RequestScoped — Объем HTTP-запроса
- SessionScoped — HTTP Session Scope
- ApplicationScoped — общий для пользователей
- ConversationScoped — Взаимодействие с JSF
- DependentScoped — по умолчанию, наследуется от клиента
Преимущество: простота реализации, нет шаблонного кода, работает волшебным образом, любой объект может быть введен путем инъекции, автоматическая конфигурация для каждого класса
Плохо: именованная аннотация небезопасна
и уродливое: создание объекта скрыто, трудно следить за ходом выполнения, IDE должна помочь
Ссылка: | Фабричный образец от нашего партнера JCG Алекса Тидома в блоге alex.theedom . |