Стандартная задача при разработке веб-приложения: есть объект данных, требуется эти данные отобразить (вывести в HTML). В Apache Wicket данные для этого привязываются к компонентам (которые и будут заниматься отображением) с помощью моделей (реализующих интерфейс IModel).
Вероятнее всего, эту публикацию будут читать те, кто уже в курсе, но на всякий случай: главный метод из IModel, который нас интересует, это:
T getObject();
Абстракция простая и лаконичная, но не всё так просто на практике. Под катом — сказ о том, как Java 8 помогла победить многословность и небезопасность стандартных подходов.
Данные
public class User implements Serializable {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
Попытка №1: мы писали, мы писали...
Вот как можно подойти к решению поставленной задачи:
public class AbstractReadOnlyModelPanel extends Panel {
public AbstractReadOnlyModelPanel(String id, IModel<User> model) {
super(id, model);
add(new Label("name", new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
return model.getObject().getName();
}
}));
add(new Label("age", new AbstractReadOnlyModel<Integer>() {
@Override
public Integer getObject() {
return model.getObject().getAge();
}
}));
}
}
Всё дёшево, надёжно и практично: создаём анонимный субкласс AbstractReadOnlyModel. Работает быстро, выглядит понятно. Одна проблема — из-за использования того самого анонимного класса код получается громоздкий: 6 строк на компонент — не шутка.
Попытка №2: стреляем в направлении ноги
Теперь попробуем PropertyModel:
public class PropertyModelPanel extends Panel {
public PropertyModelPanel(String id, IModel<User> model) {
super(id, model);
add(new Label("name", PropertyModel.of(model, "name")));
add(new Label("age", PropertyModel.of(model, "age")));
}
}
Ух ты, гораздо компактнее. Но в бочке мёда есть немало дёгтя:
- Во-первых и в-главных — компилятор нам не помощник. Можно указать несуществующее свойство, можно указать свойство не того типа, можно сделать ещё много интересных ошибок.
- Во-вторых: рефлексия. Не критично, но её наличие не радует.
На сцену выходят лямбды
К счастью, Java 8 уже давно выпущена, и лямбды вместе с Method References уже спешат на помощь:
public class GetterModel<E, P> extends AbstractReadOnlyModel<P> {
private final E entity;
private final IModel<E> entityModel;
private final IPropertyGetter<E, P> getter;
private GetterModel(E entity, IModel<E> entityModel, IPropertyGetter<E, P> getter) {
this.entity = entity;
this.entityModel = entityModel;
this.getter = getter;
}
public static <E, P> GetterModel<E, P> ofObject(E entity, IPropertyGetter<E, P> getter) {
Objects.requireNonNull(entity, "Entity cannot be null");
Objects.requireNonNull(getter, "Getter cannot be null");
return new GetterModel<>(entity, null, getter);
}
public static <E, P> GetterModel<E, P> ofModel(IModel<E> entityModel, IPropertyGetter<E, P> getter) {
Objects.requireNonNull(entityModel, "Entity model cannot be null");
Objects.requireNonNull(getter, "Getter cannot be null");
return new GetterModel<>(null, entityModel, getter);
}
@Override
public P getObject() {
return getter.getPropertyValue(getEntity());
}
private E getEntity() {
return entityModel != null ? entityModel.getObject() : entity;
}
}
public interface IPropertyGetter<E, P> {
P getPropertyValue(E entity);
}
Ну и сразу же пример, переписанный с этой реализацией модели:
public class GetterModelPanel extends Panel {
public GetterModelPanel(String id, IModel<User> model) {
super(id, model);
add(new Label("name", GetterModel.ofModel(model, User::getName)));
add(new Label("age", GetterModel.ofModel(model, User::getAge)));
}
}
Почти так же кратко, как и в примере с PropertyModel, к тому же:
- типобезопасно: компилятор проверит, что тип совпадает (если, конечно, задействовать более разборчивый, чем Label, компонент);
- гораздо более безопасно с точки зрения возможности опечаток: если вы опечатаетесь, компилятор это скорее всего отловит;
- не используется рефлексия.
По сравнению с PropertyModel, правда, есть и некоторые недостатки:
- GetterModel — только для чтения, тогда как PropertyModel позволяет и писать. Добавление ещё и setter-а лишает задумку элегантности, к тому же добавляет ещё один источник ошибок (появляется возможность указать setter от одного свойства, а getter от другого).
- PropertyModel позволяет обращаться к вложенным свойствам с помощью выражений вида «outerObject.itsProperty.propertyOfProperty».
Зато есть приятный бонус: аналог магической возможности PropertyModel использовать в качестве источника данных и модели, и POJO реализуется без всякой магии: мы просто добавили два фабричных метода (ofModel() и ofObject()).
Ссылки
Автор: rpuch