В одной из моих последних статей о CDI один из уроков, которые я выучил, состоял в том, чтобы использовать фабричные методы, чтобы уменьшить количество классов. В этой статье я углублюсь в детали, так как считаю, что это имеет некоторое значение. Более подробную информацию о CDI можно найти в некоторых предыдущих постах: обзорная часть 1 и обзорная часть 2 .
Наивный подход
В школе вас, вероятно, учили создавать класс для каждого компонента. Например, если ваше приложение имеет 4 кнопки: север, восток, юг и запад, у вас будет 4 класса. Каждый класс настраивает свою метку, значок и свое действие либо в конструкторе, либо в каком-либо другом методе.
Примечание: в CDI вы можете аннотировать такой метод с помощью @PostConstruct, чтобы контейнер вызывал его сразу после конструктора.
При создании Swing в качестве примера получается следующий код:
public class NorthButton extends JButton {
public NorthButton() {
// Initializes label
// Initializes icon
// Initializes action
}
...
}
Общее поведение и атрибуты могут быть разделены на один суперкласс, таким образом, вы получите 5 классов.
Использование правильного количества классов очень важно в любом объектно-ориентированном языке:
- Если вы используете слишком мало классов, вы рискуете, что у каждого класса, который вы разрабатываете, слишком много ответственности и они слишком велики.
- Напротив, если вы используете слишком много классов, вы разберете код во многих местах, и это станет кошмаром обслуживания. Кроме того, в JVM Sun (Oracle?) Классы загружаются в пространство PermGen, которое имеет фиксированный размер. Кто никогда не видел страшного java.lang.OutOfMemoryError: пространство PermGen?
ИМХО, у обоих есть веские причины не создавать отдельный класс для каждого экземпляра.
Фабричный подход
Вторым логическим шагом является создание методов, которые создают правильный экземпляр. В нашем предыдущем примере это означало бы фабричный класс с 4 методами для создания кнопок, по одному для каждой кнопки и, вероятно, 5-й метод с общим поведением:
public class ComponentFactory {
@North
@Produces
public JButton createJButton() {
JButton button = new JButton();
// Set label
// Set icon
// Set action
return button;
}
...
}
Обратите внимание, что вам все равно понадобится аннотация для каждого компонента:
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
@Qualifier
public @interface North {}
В нашем случае это @North, @East, @South и @West. Если эти компоненты обнаружены в вашем приложении, хорошо (например, кнопка «Отмена»). Если нет, у вас все еще есть класс для каждого компонента (аннотации преобразуются в классы во время компиляции).
«Общий» подход аннотаций
CDI позволяет вам квалифицировать внедрение не только по типу аннотации, но и по некоторым (или всем) их элементам. Таким образом, мы можем значительно уменьшить количество необходимых аннотаций. Для этого просто создайте аннотацию к этой модели:
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
@Qualifier public @interface TypeName {
Class value();
String name(); }
Теперь фабричный метод становится:
public class ComponentFactory {
@TypeName(value = JButton.class, name = "North")
@Produces public JButton createJButton() {
JButton button = new JButton();
// Set label
// Set icon
// Set action
return button;
}
...
}
Хотя этот метод отвечает нашим требованиям, касающимся количества классов (и аннотаций), у него все же есть один существенный недостаток: теперь это число методов значительно увеличилось. Хотя это и не такой большой недостаток, как большое количество классов, он делает код менее читаемым и, следовательно, менее обслуживаемым.
Полностью «общий» подход
CDI позволяет использовать параметр точки внедрения для метода производителя. Этот параметр имеет много информации о, угадайте что … точка впрыска. Такая информация включает в себя:
- объект отражения, в зависимости от типа внедрения, соответственно Field, Method и Constructor для поля, параметра метода и параметра конструктора
- требуемый тип для инъекции
- аннотации относительно точки впрыска
Последний момент — это то, что движет тем, что будет дальше. Это означает, что вы можете аннотировать свою точку внедрения, а затем получать эти аннотации во время выполнения метода производителя. В нашем случае у нас может быть аннотация для каждого атрибута, который мы хотим установить (текст, значок и действие). Давайте создадим такую аннотацию:
Такая аннотация может использоваться в методе производителя следующим образом:
public class ComponentFactory {
@Produces
@TypeName(JButton.class)
public JButton createJButton(InjectionPoint ip) {
JButton button = new JButton();
Annotated annotated = ip.getAnnotated();
if (annotated.isAnnotationPresent(Text.class)) {
Text textAnnotation = (Text) annotated.getAnnotation(Text.class);
String text = textAnnotation.value();
button.setText(text);
}
// Set icon
// Set action
return button;
}
}
Теперь единственное, что вам нужно сделать, это аннотировать поле кнопки следующим образом:
public class Xxx {
@Inject
@TypeName(JButton.class)
@Text("North")
private JButton northButton;
...
}
Это даст нам кнопку с надписью «Север». И ничто не мешает вам сделать то же самое с иконкой и действием.
Более того, используя только текст, вы можете пойти дальше и управлять интернационализацией:
@Retention(RetentionPolicy.RUNTIME)
@Target({FIELD,METHOD,PARAMETER,TYPE})
public @interface Text {
// Key in the properties file
String value();
// Path to the resource bundle
String resourceBundle() default "path/to/resource/bundle";
}
Эта аннотация также может быть повторно использована для других компонентов, которые управляют текстом, таких как метки.
Вывод
Там нет правильного или неправильного подхода в использовании CDI. Тем не менее, последний подход имеет преимущество быть самым мощным. Кроме того, он также позволяет писать код относительно типа компонента в одном месте. Это дорога, по которой я дошел, и я ей очень доволен.