Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Groovy vs Java для JavaFX

Anna | 4.06.2014 | нет комментариев

JavaFX отлична!

Вначале пару слов об JavaFX. Чем нам понравилось с ней трудиться.

Теперешний API. Даже без «билдеров», все выглядит дюже современно.

Тотальный Data Driven Development. Обожаем это. Логика основанная на связке данных очищает код от хлама, гетеры/сеттеры — «долой!». События обработки метаморфозы данных, дву-направленный «биндинг».

FXML. Хорошая вещь для прототипирования. Внятна дизайнеру, имеется отличный визуальный инструмент, от Oracle — «JavaFX Scene Builder». Подмечу, что потом все же нам потом захотелось переписать FXML в виде обыкновенного кода. Легко поддерживать FXML будет получается труднее — неизменно необходимо менять два файла, код и FXML. Плюс когда применяется код легче пользоваться наследованием. 

Nodes. Конструкция компонентов. Дозволено бегать по дереву. Дозволено искать по lookup(). Как в DOM. Прям jQuery пиши.

CSS. Это подлинно вещь. «Скинуем» компоненты через один всеобщий css-файл. ID-шники, CSS-классы, селекторы и псевдоселекторы.

Text Engine. Дюже отличный движок для трудных текстов.

WebView. Реализуем навороченные компоненты на движке Webkit. Об этом читать предыдущую статью.

Что не дюже отлично

Это отменное. Что нехорошо? JavaFX скрипт в свое время не легко так придумали. Создавать поля для доступа к Bindable данным через гетеры и сеттеры — это какой-то шаг назад и вчерашний день. Java здесь не дюже отлична. В Java 8 есть лямбда-выражения, но возникновение их тоже результат на вопрос, что с Java что-то необходимо делать и причина задуматься о больше кардинальном решении.

Groovy!

Мы решили все эти задачи для себя предпочтя Groovy. Лаконичен, в классном смысле ветх (вызрел) и отлично поддерживается в IDEA. Groovy дозволил нам сократить объем кода раз в десять верно. Работает, выглядит и читается примерно как Java, но как же он отменен с точки зрения компактности!

Есть еще куча отличных и прекрасных языков для JVM, но так сложилось что Groovy нас устраивает. Да и любим мы скобочки, аннотации и не хотим мы что-то здесь в себе крушить. Плюс у меня лично был семи-летний навык применения Groovy, а когда есть специалист в команде, то отменнее воспользоваться, а не брать что то вовсе неизвестное.

Кстати Groovy по популярности языков (по данным TIOBE) занимает 18 место.

Наши практики

Сейчас давайте посмотрим примеры. Копируем из нашего плана, код настоящий.

Конфигурирование компонентов

Легко создаем экземпляр компонента через код и его конфигурируем.
На Java нам доводилось пошагово, строчка за строчкой, присваивать значения.

Button button = new Button();
button.setFocusTraversable(false);
button.setLayoutX(23);
button.setPrefHeight(30);
button.setPrefWidth(30);
button.setText("ADD");

Как выглядит тоже самое если переписать на Groovy?

Button button = new Button(focusTraversable: false, layoutY: 23, prefHeight: 30, prefWidth: 30, text: "Add")  

Груви, напомню, кто не знает, разрешает обращаться к способам доступа (геттерам, сеттерам) без приставки set/get. То есть если в классе есть способ setText — то его вызов производится через примитивное присвоение значения text = «Add». Плюс при компиляции Groovy классов, публичным полям добавляются геттеры и сеттеры механически. Следственно из груви не прянято вызывать способ set/get если для этого нет реальной необходимости.

А в параметры конструктора дозволено передавать пары — имя: значение (на самом деле это обыкновенных HashMap и синтаксис здесь применяется Groovy Maps — [key1:value1, key2:value]).

Причем значимо, что IDEA нам все это подсказывает, валидирует тип данных и лимитация доступа.

Такой метод конфигурации компонентов, сразу наводит на мысль на невозможно ли конфигурирвать сразу конструкцию компонентов?

Дозволено!

menus.addAll(
        new Menu(text: "File", newItems: [
                new MenuItem(
                        text: "New Window",
                        onAction: { t ->
                            ApplicationUtil.startAnotherColtInstance()
                        } as EventHandler<ActionEvent>,
                        accelerator: new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN)
                ),
                new Menu(text: "New Project", newItems: [
                        newAs = new MenuItem(
                                text: "New AS Project",
                                id: "new-as",
                                onAction: { t ->
                                    ProjectDialogs.newAsProjectDialog(scene, false)
                                } as EventHandler<ActionEvent>
                        ),
                        newJs = new MenuItem(
                                text: "New JS Project",
                                id: "new-js",
                                onAction: { t ->
                                    ProjectDialogs.newJsProjectDialog(scene, false)
                                } as EventHandler<ActionEvent>
                        )
                ]),
                new SeparatorMenuItem(),
                new MenuItem(
                        text: "Open Project",
                        onAction: { t ->
                            ProjectDialogs.openProjectDialog(scene, false)
                        } as EventHandler<ActionEvent>,
                        accelerator: new KeyCodeCombination(KeyCode.O, KeyCombination.SHORTCUT_DOWN)
                ),
                recentProjectsSubMenu = new Menu(text: "Open Recent", newItems: [
                        clearRecentProjects = new MenuItem(
                                text: "Clear List",
                                onAction: { t ->
                                    RecentProjects.clear()
                                } as EventHandler<ActionEvent>
                        ),
                ]),
                new SeparatorMenuItem(),
                save = new MenuItem(
                        text: "Save Project",
                        id: "save",
                        onAction: { t ->
                            ProjectDialogs.saveProjectDialog()
                        } as EventHandler<ActionEvent>,
                        accelerator: new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN),
                        disable: true
                ),
                saveAs = new MenuItem(
                        text: "Save As...",
                        onAction: { t ->
                            ProjectDialogs.saveAsProjectDialog(scene)
                        } as EventHandler<ActionEvent>,
                        accelerator: new KeyCodeCombination(KeyCode.S,
                                KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN),
                ),
                new MenuItem(
                        text: "Close Project",
                        onAction: { t ->
                            ProjectDialogs.closeProjectDialog()
                        } as EventHandler<ActionEvent>,
                        accelerator: new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN),
                ),
                new SeparatorMenuItem(),
                new MenuItem(
                        text: "Exit",
                        onAction: { t ->
                            ApplicationUtil.exitColt()
                        } as EventHandler<ActionEvent>
                ),
        ]),
        new Menu(text: "Help", newItems: [
                new MenuItem(
                        text: "Open Demo Projects Directory",
                        onAction: { t ->
                            ProjectDialogs.openDemoProjectDialog(scene)
                        } as EventHandler<ActionEvent>
                ),
                new MenuItem(
                        text: "Open Welcome Screen",
                        onAction: { t ->
                            ProjectDialogs.openWelcomeScreen(scene)
                        } as EventHandler<ActionEvent>
                ),
        ])
)

Такой код выглядит не менее читабельным чем FXML. Плюс здесь, на месте, дозволено описать все обработчики событий, что невозможно было бы сделать на FXML. А поддерживать такой код проще.

Динамические свойства и способы

Внимательный читатель спросить, а что за поле «newItems» у Menu? Да, такого способа у класса Menu нет. А добавили мы такой способ, потому что поле items” мы можем только читать, но не можем присваивать. У него нет способа «setItems()», а есть только «getItems()» и присваивать новое значение невозможно. Read-only. Дабы конфигурировать Menu в виде конструкции, мы добавили динамическое поле.

Добавить такое поле дюже легко, но наша Java сущность длинно противилась такой крамоле как динамические способы. Мы много напридумывали велосипедов, пока не смирились с фактом необходимости воспользоваться динамикой. А все оказалось легко и нестрашно.

Добавление динамического полей мы перенесли в обособленный класс GroovyDynamicMethods. Вот его код:

class GroovyDynamicMethods {

    private static inited = false

    static void init() {
        if(inited)return
        inited = true

        addSetter(javafx.scene.Node, "newStyleClass", { String it ->
            styleClass.add(it)
        })
        addSetter(Parent, "newChildren", {List<MenuItem> it ->
            children.addAll(it)
        })
        addSetter(Menu, "newItems", {List<MenuItem> it ->
            items.addAll(it)
        })
    }

    private static void addSetter(Class clazz, String methodName, Closure methodBody) {
        addMethod(clazz, "set"   methodName.capitalize(), methodBody)
    }

    private static void addMethod(Class clazz, String methodName, Closure methodBody) {
        ExpandoMetaClass exp = new ExpandoMetaClass(clazz, false)
        exp."$methodName" = methodBody
        exp.initialize()
        clazz.metaClass = exp
    }
}

Как вы видите нам потребовалось добавить каждого три способа для поддержки конфигурации компонентов через конструкцию.

Плюс мы обучили IDEA понимать что это динамический класс.

(img dynamic properties)

Сейчас IDEA знает о существовании таких полей, как словно они есть в API JavaFX.

Работа с Bindable свойствами

Связывание данных, восхитительна штука. У нас в команде применяется такая мантра — «Если что-то дозволено сделать через байндидинг, делай через байндинг». “… Дабы потом не переделывать”.

Бандинг разрешает связать модель данных, и компоненты. UI-компоненты сами имеют байндинг свойства, которые дозволено объединять с данными модели либо строить на изменении этих свойств логику — подписываться на события метаморфозы данных.

Легкой пример CheckBox:

CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().bindBidirectional(selectedProperty);

А здесь мы реагируем на событие нажатия на чекбокс:

CheckBox checkBox = new CheckBox();
checkBox.selectedProperty().addListener(new ChangeListener<Boolean>() {
    @Override
    public void changed(ObservableValue<? extends Boolean> value, Boolean before, Boolean after) {
        System.out.println("value = "   value);
    }
});

Применять комфортно. Не дюже комфортно такие свойства описывать.

Java предлагает такой вот сценарий (код сделан IDEA механически).

private StringProperty name = new SimpleStringProperty(); // сделали качество

//даем ссылку на качество наружу (но не даем его изменять наружно)
public StringProperty nameProperty() {
    return name;
}

// дозволено взять значение
public String getName() {
    return name.get();
}

// даем вероятность присвоить свойству новое значение
public void setName(String name) {
    this.name.set(name);
}

Все бы отлично, да и IDE для нас такой код генерирует. Ну тупость ли? Отчего нам все это необходимо видеть? За каждому этим хламом мы не видим нашей логики.

Решение! Берем AST трансформацию, которая для нас данный код генерирует. При компиляции.

Наше качество (которое мы описали в Java 10 строками) превращается в Groovy в одну строку и выглядит так:

@FXBindable String name;

@FXBindable вы можете взять добавив Jar-ку GroovyFX в свой план.
Мы сделали форк такой аннотации и вы можете ее взять у нас на гитхабе.

Плюс в этом же плане вы найдете файл с растяжением .gdsl, тот, что обучит IDEA пользоваться этой аннотацией — автокомлит и т.д.

Такая трасформация, так же создает способы setName, getName, getNameProperty. Плюс к этому еще добавляется способ name() тот, что разрешает получить доступ к полю написав еще поменьше букв. Вкусовщина, но мы Почаще каждого пользуемся именно этим способом.

this.nameInput.textProperty().bindBidirectional(this.name()) // this.name() - это наше строковое поле name

Долой неизвестные классы

В примере с Menu мы подписываемся на события через неизвестные классы. На примере конструкции меню видно, что обработчиком событий выступает «кложура».

onAction: { t ->
    ProjectDialogs.newAsProjectDialog(scene, false)
} as EventHandler<ActionEvent>

Каждая магия в «as EventHandler» — тело кложуры перемещается в тело способа handle, класса EventHandler. Применение такой короткой записи для обработки событий делает код чище. Кстати мудрая IDEA предлагает квикфис «Change to dynamic instantiation». Так же дозволено применять иную записать — через Map ([handler1: {}, handler2: []]), если класс обработчик требует перегрузить несколько способов.

Работа с XML

В нашем плане нам необходимо было сериализовать модель данных в XML и брать ее с диска. Вначале хотели по повадке воспользоваться XStream, но нам необходима была больше управляемая конструкция — Bindable свойства JavaFX они крупные, а конвертеры писать лень. Посмотрели JAXB, тоже нехорошо. Тоже и с Groovy XML-сериализацией.

Подошел ексте — мы знаем что за нас при компиляции производятся AST трансформации и мы при написании кода полагаем, что к какой-то конструкции за нас что-то добавляется автоматом. Такая вот Java с автогенерацией. И огромнее ничего нам не необходимо.

Сайт плана codeorchestra.com

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB