[Из песочницы] Wicket+лямбды: типобезопасная и лаконичная реализация IModel

Стандартная задача при разработке веб-приложения: есть объект данных, требуется эти данные отобразить (вывести в 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 model) { super (id, model);

add (new Label («name», new AbstractReadOnlyModel() { @Override public String getObject () { return model.getObject ().getName (); } })); add (new Label («age», new AbstractReadOnlyModel() { @Override public Integer getObject () { return model.getObject ().getAge (); } })); } } Всё дёшево, надёжно и практично: создаём анонимный субкласс AbstractReadOnlyModel. Работает быстро, выглядит понятно. Одна проблема — из-за использования того самого анонимного класса код получается громоздкий: 6 строк на компонент — не шутка.Попытка №2: стреляем в направлении ноги Теперь попробуем PropertyModel: public class PropertyModelPanel extends Panel { public PropertyModelPanel (String id, IModel 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 extends AbstractReadOnlyModel

{ private final E entity; private final IModel entityModel; private final IPropertyGetter getter;

private GetterModel (E entity, IModel entityModel, IPropertyGetter getter) { this.entity = entity; this.entityModel = entityModel; this.getter = getter; }

public static GetterModel ofObject (E entity, IPropertyGetter getter) { Objects.requireNonNull (entity, «Entity cannot be null»); Objects.requireNonNull (getter, «Getter cannot be null»);

return new GetterModel<>(entity, null, getter); }

public static GetterModel ofModel (IModel entityModel, IPropertyGetter 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 { P getPropertyValue (E entity); } Ну и сразу же пример, переписанный с этой реализацией модели: public class GetterModelPanel extends Panel { public GetterModelPanel (String id, IModel 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 ()).Ссылки Apache Wicket Framework Java: Lambda Expressions Java: Method References

© Habrahabr.ru