Статьи

Java везде: пиши один раз, везде с DukeScript

Уже довольно давно Java не справляется с обещанием «пиши один раз, беги куда угодно». DukeScript хотел бы изменить это, включив четкое разделение представления и логики в кроссплатформенных приложениях. В этой статье простой сценарий используется для ознакомления с основами DukeScript.

В течение многих лет Java Swing позволяла разработчикам писать приложения, которые могли бы работать в любой операционной системе. Все это закончилось с появлением смартфонов, планшетов и встроенных компьютеров. На предприятии настольный компьютер доминировал многие годы. В то же время, однако, каждый ИТ-проект включает планы на будущее, когда приложение должно быть перенесено на мобильные платформы. Создание собственных приложений для всех платформ требует специальных навыков и является дорогостоящим с точки зрения как обслуживания, так и разработки. Что надо сделать?

DukeScript ( DukeScript.com ) снова предлагает вам решение на основе Java, позволяющее разрабатывать кроссплатформенные приложения. DukeScript обеспечивает четкое разделение представления и логики, позволяя дизайнерам пользовательского интерфейса сосредоточиться на разработке пользовательского интерфейса, а кодировщикам — на написании хорошо протестированного кода приложения.

Лучший из двух миров

Основная идея DukeScript проста. Каждая операционная система предлагает возможность запуска основных приложений Java. На Android это изначально делается с помощью Dalvik Runtime и ART , на iOS у вас RoboVM , а на многих других платформах у вас OpenJDK и Oracle SE Embedded . Одновременно для браузера доступен ряд виртуальных машин Java ( TeaVM , Doppio , Bck2Brwsr ), которые работают без подключаемого модуля браузера. Однако здесь не хватает технологии унифицированного представления, в то же время современный компонент рендеринга HTML доступен практически на всех платформах. Когда вы объединяете эти разрозненные технологии, виртуальные машины и компоненты вместе, вы получаете основу всеобъемлющей структуры.

Объединив все эти части вместе, все их преимущества могут быть использованы одновременно. Например, Java имеет из всех языков программирования лучшую поддержку IDE и позволяет писать код, который можно поддерживать, и легко реорганизовывать благодаря его статической типизации. По этим причинам он лучше подходит для использования в больших проектах, чем JavaScript. Со стороны пользовательского интерфейса, для HTML и CSS, у вас есть доступ к арсеналу бесплатных и коммерческих платформ и сервисов. Когда пользовательский интерфейс и бизнес-логика четко отделены друг от друга, мы можем использовать весь арсенал без каких-либо исключений и ограничений. Чтобы продемонстрировать эти моменты, сейчас мы собираемся разработать и оформить приложение To Do List.

ViewModel

DukeScript использует шаблон проектирования Model-View-ViewModel (MVVM) для разделения визуализации и логики. Представление определено на языке разметки и декларативно связывает активные элементы со свойствами ViewModel. Благодаря этой архитектуре ViewModel не нужно знать о представлении. Без каких-либо изменений, View может быть заменен из ViewModel. Вся логика View определяется в ViewModel. В шаблоне MVVM Модель является оставшейся частью приложения, в то время как способ визуализации Модели не определен и, следовательно, не ограничен.

epple_dukescript_1a

Давайте начнем с ViewModel. В листинге 1 показано, как создается модель представления. Аннотация @Model гарантирует, что будет создан класс с именем Task . В то же время, автоматически устанавливаются установщики и получатели для заголовка и завершения свойств. Это избавляет разработчика от необходимости писать кучу кода, а структура класса ViewModel компактна и понятна с одного взгляда. Процесс создания происходит автоматически в фоновом режиме, поэтому класс доступен сразу во время разработки в IDE.

Листинг 1

1
2
3
4
5
@Model(className = "Task", properties = {
@Property(name = "title", type = String.class),
@Property(name = "complete", type = boolean.class)
})
public static class TaskModel {}

Для более сложных задач мы можем обернуть нашу модель. В листинге 2 показан TaskListViewModel , представляющий список задач вместе с дополнительными свойствами. Аннотация @Function помечает методы, которые можно вызывать из представления.

Перечисление 2

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Model(className = "TaskListViewModel", properties = {
  @Property(name = "input", type = String.class),
  @Property(name = "tasks", type = Task.class, array = true),
  @Property(name = "editing", type = Task.class)
}, targetId = "body")
final class TaskListViewModelDefinition {
  
  @Function
  public static void editTask(TaskListViewModel list, Task data) {
    list.setEditing(data);
  }
  
  @Function
  public static void stopEditing(TaskListViewModel list) {
    list.setEditing(null);
  }
  
  @Function
  @ModelOperation
  public static void deleteTask(TaskListViewModel model, Task data) {
    model.getTasks().remove(data);
  }
  
  @Function
  @ModelOperation
  public static void addTask(TaskListViewModel model) {
    if (null == model.getInput() || model.getInput().length() == 0) {
      return;
    }
    Task task = new Task(model.getInput(), false);
    model.setInput("");
    model.getTasks().add(task);
  }
}

Модульные тесты с DukeScript

Два метода в приведенном выше коде помечены как @ModelOperation . В DukeScript вы делаете это с помощью методов, которые также должны вызываться извне View, который в нашем примере (листинг 3) является модульным тестом. Тест показывает, как сгенерированный ViewModel может быть использован. В первом тестовом примере мы моделируем ввод новой задачи пользователем, который вводит задачу ( setInput ) и подтверждает ввод, например, с помощью кнопки или клавиши Enter ( addTask ). Хотя View еще нет, мы уже можем протестировать методы ViewModel. Этот сценарий очень хорошо показывает чистое разделение компонентов.

Перечисление 3

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public class TodoListTest {
  @Test
  public void testAddTask() {
    TaskListViewModel taskList = new TaskListViewModel();
    Assert.assertEquals(taskList.getTasks().size(), 0);
    taskList.setInput("Buy milk!");
    taskList.addTask();
    Assert.assertEquals(taskList.getTasks().size(), 1);
    Task task = taskList.getTasks().get(0);
    Assert.assertEquals(task.getTitle(), "Buy milk!");
  }
  @Test
  public void testDeleteTask() {
    TaskListViewModel taskList = new TaskListViewModel();
    taskList.getTasks().add(new Task("Buy milk!", false));
    Assert.assertEquals(taskList.getTasks().size(), 1);
    Task task = taskList.getTasks().get(0);
    taskList.deleteTask(task);
    Assert.assertEquals(taskList.getTasks().size(), 0);
  }
}

Сериализация с JSON

Когда вы смотрите на аннотации, которые определяют классы ViewModel, вы должны заметить, что они выглядят немного как сообщения JSON. Это не случайно, поскольку DukeScript придает большое значение возможности простой интеграции с JSON. Метод toString класса ViewModel возвращает строку JSON. Так же легко вы можете создать объект ViewModel из строки JSON. В листинге 4 показано, как ViewModel снова сериализуется и десериализуется. Когда все, что вам нужно, это копия объекта, используйте вместо этого метод clone . Цель Models.parse — десериализация сообщений с сервера или локально сохраненных данных .

Листинг 4

01
02
03
04
05
06
07
08
09
10
TaskListViewModel copy;
String json = original.toString();
InputStream inputStream = new ByteArrayInputStream(
  json.getBytes(StandardCharsets.UTF_8));
try {
  copy = Models.parse(BrwsrCtx.findDefault(TaskListViewModel.class),
  TaskListViewModel.class, inputStream);
} catch (IOException ex) {
  Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}

Вид

По умолчанию DukeScript использует HTML для определения представления. Благодаря чистому отделению View от ViewModel можно использовать и другие форматы. Проект dukescript-javafx демонстрирует, как это работает. В этом проекте DukeScript ViewModels можно легко визуализировать в приложениях JavaFX. Язык, используемый для определения представлений, в данном случае — FXML. Controls.js также использует альтернативный формат для определения Views, который будет обсуждаться позже в этой статье.

Для приложения To Do List, которое мы создаем в этой статье, мы просто будем использовать стандартный формат HTML, показанный в листинге 5. Элементы, зависящие от ViewModel, используют атрибут data-bind . Таким образом, они декларативно связаны со свойствами и методами ViewModel. Кроме того, циклы for-each и условные операторы могут быть определены таким образом.

В большинстве случаев атрибут data-bind делает все, что вам нужно. Однако иногда вам потребуется создать элемент HTML, хотя этот элемент HTML может не потребоваться для представления. Для этих случаев есть специальные комментарии. В примере, приведенном в листинге 5 ниже, через <! — ko foreach: tasks -> мы перебираем список задач , а <! — / ko -> закрывает цикл. Подробнее о синтаксисе связывания можно узнать из комментариев в листинге 5, а также с сайта DukeScript .

Листинг 5

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<ul>
    <!-- Iterate over the List of Tasks in the TaskListViewModel -->
    <!-- ko foreach: tasks -->
    <li>
        <!-- When the Task is not being edited... -->
        <!-- ko ifnot: $root.editing()===$data -->
        <!-- ...bind the checkbox state to the Task property named "complete" -->
        <input type="checkbox" name="" data-bind="checked: complete"/>
        <!--...bind the text of the span to the Task property named "title" -->
        <span data-bind="text: title"></span>
        <span class="btns">
            <!-- ...on click, call the 'editTask' method -->
            <button  data-bind="click: $root.editTask">Edit</button>
            <!-- ...on click, call the 'deleteTask' method -->
            <button  data-bind="click: $root.deleteTask">Delete</button>
        </span>
        <!-- /ko -->
        <!-- When the Task is being edited, show an input field... -->
        <!-- ko if: $root.editing()===$data -->
        <!-- ...on Submit (Enter) call the 'stopEditing' method -->
        <form data-bind="submit: $root.stopEditing">
            <!-- ...bind the entered text to the Task property named "title"`-->
            <input type="text" data-bind="textInput: title"/>
        </form>
        <!-- /ko -->
    </li>
    <!-- /ko -->
    <li>
        <!-- On Submit (Enter) call the 'addTask' method... -->
        <form data-bind="submit: addTask">
            <!-- ...bind the entered text to the Task property named "input" -->
            <input type="text" data-bind="textInput: input"/>
        </form>
    </li>
</ul>

Действующий прототип, определенный выше, отображает, как показано ниже. Визуально это может показаться немного скромным, поэтому давайте изменим это в следующих разделах!

eppleton-английский-4

Эксперимент дизайнер / разработчик

До появления DukeScript появилось несколько других фреймворков с обещанием возможности отделить дизайн от разработки. В реальном мире от этого обещания мало что остается. Дизайн, как правило, требует запатентованных инструментов, которые вызывают не больше, чем усталый смех у профессиональных дизайнеров. Примером этого является JavaFX. С помощью JavaFX Scene Builder вы не можете даже создать многоугольник, в то время как будущее этого инструмента совсем не ясно . Более того, задача создания дизайна, как правило, остается за разработчиком, которому нужно потратить время на освоение целого ряда различных и противоречивых инструментов.

Чтобы проверить, работает ли все это лучше с DukeScript, я поставил перед собой небольшую задачу. Я начал с поиска и покупки готового дизайна для моего списка задач, показанного на скриншоте ниже.

eppleton-английский-3

Затем я посмотрел вокруг, чтобы кто-нибудь изменил PSD-файл на HTML (вы можете найти сотни сервисов для этой задачи в Интернете, когда будете искать «PSD to HTML»). В итоге я выбрал Rapidxhtml, потому что эта услуга дешевая и, несмотря на это, получила положительные отзывы.

Для «реального» проекта и для обеспечения лучшего общения я бы определенно предпочел прямое взаимодействие с дизайнером. Однако для этого эксперимента преимуществом является то, что общение ограничивается кредитными картами и веб-формами, поскольку таким образом мы можем гарантировать, что разработчик не знает о каких-либо особых требованиях, которые может потребовать DukeScript.

Я загружаю PSD-файл на сайт и указываю свои требования к стилю. Например, флажки и полосы прокрутки стоят дороже, поскольку применяемый стиль более сложный. По этой причине я решил не идти на этот более сложный выбор. В конце концов, полный заказ не так уж и дорог. На страницу затраты на конверсию, включая все выбранные дополнительные функции (изменение ширины, HTML5 с CSS3 и т. Д.), Составляют около 170 евро. Оплата производится заранее, и доставка, в принципе, в течение 24 часов. Звучит хорошо, и я ждал в ожидании.

Уже через 6 часов я получил электронное письмо со ссылкой на дизайн. Неплохо. На первый взгляд результат выглядел неплохо, хотя изменение ширины не сработало. Через два часа, после того, как я отправил свои комментарии к обзору, у меня появилась новая версия с правильно работающим изменением размера. На скриншоте ниже показан результат.

epple_dukescript_4

Дальнейшие замечания о небольших недостатках были проигнорированы. Для «настоящего» проекта лучшим выбором может стать услуга премиум-класса или дизайнерское агентство с соответствующими предложениями. В листинге 6 показан полученный мной HTML-код.

Листинг 6

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
    <head>
        <title>TODO</title>
        <meta name="robots" content="index, follow">
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="author" content="RapidxHTML" />
        <link rel="stylesheet" href="css/normalize.css">
        <link rel="stylesheet" href="css/style.css">
    </head>
    <body>
        <!--[if lt IE 7]>
        <p class="chromeframe">You are using an outdated browser.
        <a href="http://browsehappy.com/">Upgrade your browser today</a>
        or <a href="http://www.google.com/chromeframe/?redirect=true">install
        Google Chrome Frame</a> to better experience this site.</p>
        <![endif]-->
        <!-- box -->
        <div id="box">
            <div class="box-cont">
                <header class="box-header">
                    <div class="box-title">My tasks for today</div>
                    <div class="box-links">
                        <a href=""><img src="images/btn-cal.png" alt="" /></a>
                        <a href=""><img src="images/btn-settings.png" alt="" /></a>
                    </div>
                </header>
                <section class="todo">
                    <section class="todo-bg">
                        <ul class="todo-list">
                            <li class="done">
                                <input type="checkbox" name=""
                                       class="toggle" checked="checked" />
                                Design a to-do list
                                <span class="btns">
                                 <a href=""><img src="images/icon-edit.png" /></a>
                                 <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                            <li><input type="checkbox" name="" class="toggle" />
                                Design a super task<br />with 2 lines
                                <span class="btns">
                                  <a href=""><img src="images/icon-edit.png" /></a>
                                  <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                            <li><input type="checkbox" name="" class="toggle" />
                                fix the dog toy
                                <span class="btns">
                                  <a href=""><img src="images/icon-edit.png" /></a>
                                  <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                            <li><input type="checkbox" name="" class="toggle" />
                                buy coffee <span class="btns">
                                  <a href=""><img src="images/icon-edit.png" /></a>
                                  <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                            <li><input type="checkbox" name="" class="toggle" />
                                feed the dog <span class="btns">
                                  <a href=""><img src="images/icon-edit.png" /></a>
                                  <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                            <li><input type="checkbox" name="" class="toggle" />
                                take a walk with the dog
                                <img src="icon_smile.gif"
                                     alt=":)"
                                     class="wp-smiley">
                                <span class="btns">
                                  <a href=""><img src="images/icon-edit.png" /></a>
                                  <a href=""><img src="images/icon-delete.png" /></a>
                                </span>
                            </li>
                        </ul>
                    </section>
                </section>
            </div>
        </div>
        <!-- / box -->
        <script type="text/javascript" src="js/jquery.js"></script>
        <script type="text/javascript" src="js/modernizr-2.6.2.min.js"></script>
    </body>
</html>

Затем я должен был добавить жизнь в интерфейс. Привязки, которые вы видите ниже, вы уже знакомы с прототипом. В листинге 7 показан переписанный код.

Листинг 7

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<head>
    <title>TODO</title>
    <meta charset="utf-8">
    <link rel="stylesheet" href="css/normalize.css">
    <link rel="stylesheet" href="css/style.css">
</head>
<body id="body">
    <!-- box -->
    <div id="box">
        <div class="box-cont">
            <header class="box-header">
                <div class="box-title">My tasks for today</div>
                <div class="box-links">
                    <a href=""><img src="images/btn-cal.png" alt="" /></a>
                    <a href=""><img src="images/btn-settings.png" alt="" /></a>
                </div>
            </header>
            <section class="todo">
                <section class="todo-bg">
                    <ul class="todo-list" >
                        <!-- ko foreach: tasks -->  
                        <li>
                            <!-- ko ifnot: $root.editing()===$data -->
                            <input type="checkbox" name="" class="toggle"
                                   data-bind="checked: complete"/>
                            <span data-bind="text: title"></span>
                            <span class="btns">
                                <img src="images/icon-edit.png" alt=""
                                     data-bind="click: $root.editTask"  />
                                <img src="images/icon-delete.png" alt=""
                                     data-bind="click: $root.deleteTask" />
                            </span>
                            <!-- /ko -->
                            <!-- ko if: $root.editing()===$data -->
                            <form data-bind="submit: $root.stopEditing">
                                <input type="text" data-bind="textInput: title"/>
                            </form>
                            <!-- /ko -->
                        </li>
                        <!-- /ko -->
                        <li>
                            <form data-bind="submit: addTask">
                                <input type="text" data-bind="textInput: input"/>
                            </form>
                        </li>
                    </ul>
                </section>
            </section>
        </div>
    </div>
    <!-- / box -->
</body>
</html>

На скриншоте ниже вы можете увидеть результат.

eppleton-английский-1

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

Таким образом, разработчик может полностью сосредоточиться на реализации необходимой функциональности и сосредоточиться на бизнес-логике приложения. Те, кто имеет опыт разработки настольных приложений, знают, что на преобразование дизайна приложений регулярно тратится много времени. С DukeScript вы можете с уверенностью делегировать эти проблемы профессионалам дизайна, что освобождает ценное время для реализации функциональных требований приложения.

Как разрабатывать приложения DukeScript

В настоящее время DukeScript поддерживает различные настольные платформы, а также iOS, Android и браузер. Доступные архетипы Maven создают для каждой поддерживаемой платформы отдельный подпроект. Для каждой платформы доступны специальные задачи для тестирования и упаковки подпроекта. Например, подпроекты для Android и iOS предлагают возможность их запуска в симуляторе или на подключенном устройстве, в то время как подпроект для браузера автоматически создает статический веб-сайт.

Недавние усовершенствования позволили поддержку встроенных платформ, позволяющих использовать DukeScript в приложениях IoT. Поэтому, поскольку Oracle прекратила поддержку JavaFX на встроенных платформах , теперь снова появилась возможность разработки профессиональных графических интерфейсов с Java на встроенных устройствах. В этих случаях OpenJDK обычно достаточно в качестве JVM, поэтому дорогостоящее лицензирование Java SE не требуется даже для коммерческих проектов.

С помощью архетипов Maven разработка приложений DukeScript может осуществляться с помощью различных Java IDE. Специально для среды IDE NetBeans также имеется плагин с рядом вспомогательных функций, которые делают разработку еще более удобной . Например, в редакторе HTML есть завершение кода для директивы привязки данных, а инспектор DOM позволяет вам проверять работающее приложение. Изменения в HTML и CSS автоматически фиксируются работающим приложением. Начиная с версии 0.8, в архетипы Maven была введена даже горячая замена. Изменения в коде автоматически внедряются в работающее приложение и могут быть немедленно протестированы, как показано на скриншоте ниже. Даже разработчики JavaScript должны ревновать, поскольку — в отличие от разработки JavaScript — состояние приложения поддерживается без какой-либо ручной перезагрузки.

eppleton-английский-2

Controls.js для Java — DukeScript без HTML

DukeScript стремится включить кроссплатформенную разработку без JavaScript. Как правило, интерфейс приложений DukeScript написан с помощью HTML и CSS. Этот аспект разработки приложения, как было показано, можно делегировать, хотя он по-прежнему требует написания тестов и адаптации для различных платформ, а также ручного редактирования файлов HTML.

В качестве альтернативного подхода проект Controls.js для Java позволяет разработать полный пользовательский интерфейс с помощью перетаскивания. При таком подходе у вас есть архетипы Maven и плагин NetBeans для поддержки вас. Controls.js использует собственную библиотеку компонентов. Каждый отдельный элемент управления можно визуализировать с помощью скинов, а редактор скинов предназначен для создания пользовательских скинов. ViewModel остается прежним, а привязка выполняется с помощью визуальных редакторов.

epple_dukescript_7

Вывод

В заключение давайте рассмотрим вопрос на миллион долларов: «Подходит ли DukeScript для моего проекта?» Преимущества очевидны.

  • Используя общую кодовую базу, приложения могут разрабатываться для многих платформ.
  • Рабочий процесс хорошо структурирован, а богатые и хорошо зарекомендовавшие себя инструменты помогут вам.
  • Задачи проектирования могут быть делегированы, что значительно минимизирует затраты на обслуживание и разработку приложений.

Несмотря на это, DukeScript — не лучшее решение для каждого приложения. Те, кто ценит собственный брендинг вместе с единым кроссплатформенным дизайном, лучше обслуживаются DukeScript, чем те, кто интересуется собственным внешним видом и ощущением своего приложения на каждом устройстве, на котором оно будет развернуто. Я также не стал бы пытаться создавать инструмент трехмерного моделирования с помощью DukeScript или какое-либо приложение, которое должно использовать оптимизированные конвейеры рендеринга.

Однако DukeScript — очень хороший выбор для бизнес-приложений. Для простых бизнес-приложений полезно использовать Controls.js для Java, который обеспечивает быстрый рабочий процесс разработки приложений. Кроме того, для приложений с серверной серверной частью DukeScript хорошо подходит благодаря своим простым механизмам связи. В целом, DukeScript предлагает разработчикам Java плавный вход в кроссплатформенную разработку, не требуя отказа от самого широко используемого в мире языка программирования, который статически типизирован с лучшей поддержкой IDE, безусловно, лучше, чем любой другой язык программирования в мире сегодня.