shkolakz.ru 1
Технические замечания для Java-разработчиков проекта NumLabs. Версия 0.2

1Назначение фреймворка Lab


Большинство пакетов проекта Lab (находящиеся внутри пакета mipt.crec.lab) представляют собой фреймворк среднего уровня универсальности, который позволяет быстро делать «лабоподобные» программы на любые темы. «Лабоподобными» называются программы, редактирующие отдельные объекты – «варианты» (загружая и сохраняя их), но без возможностей, типичных для информационных систем: без навигации по множественным объектам (за исключением навигации для выбора свойств основного объекта), без их табличного представления, без поиска по критериям. Также лабоподобные программы не похожи на офисоподобные приложения (в частности, не содержат поддержки сложных настраиваемых меню, панели инструментов, специального буфера обмена, поддержки undo/redo). Редактирование отдельных свойств объекта в лабоподобных программах изменяет основной объект сразу (нет открывающихся форм и кнопок OK/Отмена в них; все, что есть в программе, пользователь легко видит). И вообще лабоподобные программы предназначены для минимального уровня пользователя и для минимального числа действий пользователя.

Дополнительными особенностями фреймворка mipt.crec.lab являются:

  • возможности, типичные для расчетных программ (графики, понятие выходных данных),

  • настройка конкретных наборов модулей программы через метаданные (доступные для редактирования опытным пользователям),

  • максимально быстрая адаптация программы к изменению/добавлению свойств редактируемого объекта,

  • поддержка специальной read-only панели основных свойств («панели варианта»), автоматическое выделение упрощенной (в т.ч. без функций сохранения) версии программы («демо-версии») для показа в апплете.

2Структура пакетов проекта Lab


  • корневой пакет – mipt.crec.lab – самые общие интерфейсы, не касающиеся view (Swing) и модели (Data)
  • .io – интерфейсы и классы для ввода/вывода, единообразного во всем приложении (способ загрузки файлов в Web-версии отличается от не-Web, а сохранение файлов отсутствует). Не используется лишь для загрузки ресурсов GUI (для этого см. .gui)


  • .data – интерфейсы и частичные реализации (наследующиеся от корневого пакета, пакетов gui и io), которые зависят от Data-фреймворка. Пакет имеет «филиалы» – подпакеты data в некоторых других пакетах (это чтобы не перегружать один пакет).

  • .gui – самые общие классы, касающиеся GUI, хранение иконок и строк для GUI

  • .gui.help справочная система (навигация по HTML) – в данной версии не нужна, т.к. будем пользоваться внешним HTML-броузером. Даже когда библиотека будет дореализована, переименуется в mipt.gui.help.

  • .gui.actions – общие для всех модулей действия, прежде всего – действия меню и сами меню (не являющиеся частью модулей; по этой причине пакет размещен не в .common!). Меню используется только из startup.appl (т.е. не в апплетах); имеет контроллеры для всех пунктов меню.

    Контроллеры действий – реализации шаблона Command, хранящиеся в подпакете .gui.actions.commands (см. mipt.aaf.command). Справочные пункты реализуются через mipt.common.HelpManager (получают ссылку на Module, за исключением стандартного пункта About). Пункты меню File зависят от Data-модели, получают ссылку на DataModule, что позволяет открывать/сохранять варианты (следят также за их измененностью – возможно, с помощью mipt.aaf.edit.data!). С помощью спец. интерфейсов для модулей пункты меню Settings (за исключением стандартной настройки LAF) получают доступ к набору моделей частей модуля – типа графиков (для их редактирования), а пункты меню Computation – доступ к контролю над расчетом (start/pause/stop). Последние команды размещаются не только в меню, но и на специальной панели расчета, размещаемой в панели .common.dataview (два варианта панели расчета – с текстовыми (без паузы) и с графическими (с паузой) кнопками – размещаются в пакете .gui.actions, – там же, где другие view и остальные необходимые для действий классы, за исключением конкретных команд пакета .gui.actions.commands)


  • .gui.gen – «общий документ» (операции над лабами и вариантами). Мало зависит от общего фреймворка

  • .gui.startup – то, что касается запуска лаб, в т.ч. абстрактный апплет, размещающий в себе лабу и абстрактное приложение, запускающее лабу отдельно (без «общего интерфейса»; такая версия с меню, но без операций над вариантами является промежуточной между полной версей и Web-версией). Данный пакет может не использоваться только для построения лабы ODEIVP (которую нет смысла существенно переделывать)

  • .gui.startup.mdi – многооконный «общий интерфейс» – контейнер лаб (приложение с main, реализация на базе проекта AXIS)

  • .gui.startup.data – классы (в т.ч. с main), удобные для тестирования без создания своих «запускальщиков». Каждый может «портить» их как угодно в своем workspace

  • .gui.startup.applet – набор апплетов для конкретных лаб.

  • .common (с подпакетами) – то, что используется в нескольких (или даже всех) конкретных лабах, но не имеет отношения к обязательной структуре лабы (другими словами, это внутреннее дело лабы, использовать ли этот повторно используемый код или нет). Подпакетам соответствуют, в частности, реализации модулей, являющихся одинаковыми частями многих лаб (логики модулей и их внешних представлений) – графиками (.common.graph), поддержкой расчетного процесса (.common.compute), «панелью варианта» или другими стандартными/настраиваемыми представлениями объекта Data (.common.dataview), формами (.common.form). А также подпакетам соответствуют реализации модулей, являющихся контейнерами для других модулей (.common.containers)
  • .compmath – корневая директория для пакетов с модулями конкретных лаб по вычислительной математике. Каждый пакет должен иметь подпакет gui (в котором хранятся View всех модулей, а также ресурсы: Bundle и *.gif). В самом пакете (на 1 уровень выше gui) должны быть классы Module и ModuleBuilder (для всех подмодулей модуля), где Name – одно слово типа Problem или Scheme. Для основного модуля эти классы лучше называть без слова Module: и Builder, где ID – код лабы. Если лаба имеет свою сложную математику (которую нецелесообразно размещать в mipt.math), ее необходимо выделять в подпакет math.

3Требования к разработчикам

3.1Какие следует использовать настройки среды программирования?


  1. Настройки компилятора: Java 5.0 (рекомендуется использовать generics + enums + java.math, но не annotations). Однако внешние проекты (в т.ч. mipt.gui.graph) – Java 1.4

  2. Обязательные комментарии к классам и методам (настроить автоматическую генерацию; non-javadoc-style-комментарии запрещены); комментарии к тривиальным методам могут быть пустые; в комментарии к классам – «@author Фамилия», в комментариях к переопределяемым методам «@see …» (и больше ничего); комментарии к файлам (которые есть по умолчанию и содержат дату создания) отсутствуют. Комментарии – по-русски (однако внешние проекты – по-английски). Другие правила написания комментариев – http://java.sun.com/j2se/javadoc/writingdoccomments/.

  3. Стиль кодирования стандартный: http://java.sun.com/docs/codeconv/. Рекомендуется следующее размещение членов класса: статические, затем нестатические поля, затем конструкторы, затем статические методы, затем нестатические методы (упорядоченные по смыслу, а не по модификаторам).

  4. В качестве «наполнителя» отступов в строке обязательно использовать табуляции, а не пробелы.

3.2Какие следует использовать настройки среды программирования?




4Требования и рекомендации по использованию чужого кода

4.1Какие классы наследовать из фреймворка mipt.crec.lab?

  1. Модули (реализации Module), как минимум, должны создаваться для подмодулей – контроллеров форм (формами называются редакторы свойств объектов, включая списковые свойства (показываемые в таблицах)). Даже если для основного модуля лабы используется контейнер (DataContainerModule), от него часто необходимо наследовать свой подкласс, и в этом подклассе определять такие действия как запуск расчетов (т.е. реализовывать интерфейс ComputingModule). Следует заметить, что ни одна лаба не должна иметь собственных графических компонентов для запуска/остановки расчетов: с целью единообразия они вынесены в специальные классы пакета .gui.actions, который и вызывает расчетные методы у ComputingModule. Для того чтобы одновременно могли редактироваться все графики лабы, достаточно, чтобы какие-то классы (под)модулей реализовывали интерфейс GraphModule, возвращая в совокупности модели всех графиков. Класс основного модуля лучше называть по идентификатору лабы, а классы дочерних модулей – с суффиксом Module. Впрочем, можно называть как угодно, однако следует переопределять getID(). ID основного модуля лабы без учета регистра должен совпадать с названиями последней части пакета лабы (а с учетом регистра эти ID написаны в начале .gui.resources.Bundle), ID дочерних модулей должен иметь формат “IDmain_IDpart”, где IDmain – ID основного модуля.


  2. Реализации ModuleView обычно имеет смысл создавать только для нестандартных подмодулей: для форм, для нетипичных представлений (например, график пространства неопределенных коэффициентов для некоторых выбираемых методов), для мест GUI с нетипичной компоновкой (которую нецелесообразно формировать с помощью готовых ContainerModuleView) или с динамически меняющейся компоновкой (например, показ/скрытие чего-либо при выборе объекта с определенными свойствами в списке/дереве или при вводе определенного значения свойства в форме).

  3. Модели – отсутствуют! Везде четко разделяются controllers и view, но вместо конкретных «моделей» иcпользуется стандартная SingleDataModel, что сделано с целью повышения гибкости проекта: изменения данных (модели) вносятся только в 2 места: непосредственно в место использования данных и в (дефолтный) XML-файл с данными модели.

  4. Построители (ModuleBuilder) обычно должны иметься для любого модуля. Хотя (нежелательным) исключением может быть отсутствие построителя основного модуля лабы (в качестве которого может быть использован стандартный ContainerModuleBuilder, по умолчанию создающий SplitModuleView; в недоделанных версиях также удобно применять заглушку для построителя неосновного модуля – ModuleBuilderStub). Набор построителей выдается ModulesDefinition, но это уже важно лишь для запуска программы (см. пункт 5) либо для построения лабы из частей (с помощью ContainerModuleBuilder). Реализация MetaDataModulesDefinition умеет (точнее, будет уметь, когда XMLDataIODelegate будет написан) конфигурироваться из файла метаданных (по умолчанию startup.xml, но у каждой лабы он должен быть в своей директории).
  5. Для независимого запуска лабы необходимо создавать отдельные классы с методом main (из «общего документа» лабы будут открываться еще не скоро, а запускальщик апплетных версий не может протестировать многие возможности). Имя классов с main рекомендуется начинать с ID модуля, а заканчивать либо словом Main (для полноценных построителей, использующих инициализационные файлы и т.п.), либо словом Test (для временных тестов определенных частей модуля или всего модуля с временным тестовым построителем). Для упрощения написания таких классов стоит их наследовать от .gui.startup.ReflectionMain (или AbstractMain – умеет только читать командную строку, но не умеет инициализировать); есть также запускальщик Data ReflectionMain, от которого вообще наследоваться не нужно, достаточно в DataApplicationInitializer указать нужный файл (пока файлы не грузятся!) либо нужные имена (в create*Stub()). Смысл в ReflectionMain: чтобы он мог хорошо показывать splash-окно (это должно происходить до загрузки большинства классов), сам по себе Main-класс не должен ни на что ссылаться, а загрузка происходит с помощью отдельного класса – реализации Initializer / наследника ApplicationInitializer (имя этого класса должно определяться в наследнике Main). Для единообразия имя класса инициализатора должно совпадать именем Main-класса, к которому в конце добавляется Initializer.


  6. Для операций, проводимых модулем совместно с «универсальной частью» программы требуется реализовывать интерфейсы ComputingModule (запуски и остановка расчетов – см. выше), а также ComputingEventModule (получение результатов расчетов у модуля; рекомендуется реализовывать через EventListenerList) и ContextHelpView (предоставление строковых идентификаторов разделов для контекстно-зависимой справки). Связь в обратную сторону (от конкретной лабы к универсальной части) происходит через OptionsEnvironment, которая дает модулю доступ к опциям графиков (в т.ч. к относящимся для всех лаб), опциям обновления в процессе расчетов, и, главное, к GraphMouseListener (специально создаваемым для модуля) с контекстным меню, в которое модуль может добавить свои пункты либо заменить имеющиеся обработчики пунктов меню на свои обработчики. По умолчанию пункты меню показывают стандартный редактор опций, сохраняют график в файл и показывают (компактную) таблицу результатов.

4.2Какие фреймворки mipt.* в каких случаях использовать?

  1. mipt.gui.graph – для показа всех графиков – использовать обязательно. В простых случаях достаточно инициализировать весь график в *GraphModuleBuilderе, а наследовать View необязательно (см. пакет .common.graph, который содержит не только типичные построители графиков, но и обработчик стандартного пункта меню для редактирования свойств ВСЕХ графиков лабы одновременно). Следует также заметить, что внутри графиков есть свои контроллеры (ZoomSwitcher для увеличения, контроллеры пунктов контекстного меню для сохранения в файл и редактирования опций), поэтому Module (контроллер) для модуля графиков тоже обычно не нужен. В лабах, которые требуют редактирования пределов графика пользователем, есть смысл подумать о классе mipt.gui.graph.plot.output.OutputPlot (его недостаток: его код получен кодогенерацией поэтому слабо читаем и слабо изменяем). Использование Graph можно посмотреть ExampleGraphFrame (c main), использование OutputPlot – в классе Main того же пакета.


  2. mipt.aaf.edit.form.* (не зависит от swing) + mipt.aaf.edit.swing.form + mipt.aaf.edit.form.data.DataUpdater – рекомендуется использовать для крупных редакторов свойств. Пакет mipt.aaf.edit.form.mo (вместе с реализациями из swing.form) – для редактирования списковых свойств. Использование объектов Form (и связанных с ними) можно посмотреть в классе mipt.aaf.edit.swing.(Abstract)SwingEditor, который формирует GUI редактора автоматически (но сам этот класс использовать не стоит как ввиду сложности (может использовать только Антон Корчак), так и ввиду наследования им UIEditorController, который противоречит принятой концепции редактирования). MultiForms (редактирование частей объекта на разных вкладках) использовать также не стоит ввиду сложности и ввиду того, что хотя формально MultiForms решает ту же задачу, что ContainerModule* (показ на нескольких вкладках), этот подход слишком нацелен на минимизацию конкретного кода и, как следствие, недостаточно гибок.

  3. mipt.gui – имеются отдельные классы, обязательные для использования:
    а) IntegerTextField/DoubleTextField в редакторах целых и вещественных чисел (при условии использования фреймворка mipt.aaf.edit.form это происходит автоматически). Часто нужно использовать возможность сложения текущего числа или умножения текущего числа пользователем нажатием на клавишу «стрелка».
    б) OptionPane – JOptionPane, русифицированная за счет переопределенных UI; также расширен возможный набор кнопок (типа YesToAll), а также для некоторых случаев упрощен вызов основного метода, который называется option() (для ввода объектов – input()).

  4. mipt.gui.choice – рекомендуется использовать для устранения недоделок стандартных «компонентов выбора» swing (таблицы, деревья, списки и их renererы/editorы)
  5. mipt.gui.data.choice – очень удобно использовать для «компонентов выбора», если данные для них считались из настроечного файла (в виде Data). Можно совершенно не думать о компонентах и событиях swing, достаточно добавлять объекты Data () и получать события о том, что они выбираются (или «открываются») пользователем; есть и более сложные события.


4.3Какие внешние фреймворки в каких случаях использовать?







Технические замечания для Java-разработчиков проекта NumLabs. Версия 0.2