Слова истины просты

Еврипид

Концепция объектно-ориентированного программирования одновременно проста и сложна. Её простота в лаконичности решений: иерархическая структура описания множества сущностей с их свойствами, связями и возможностями взаимодействия с другими сущностями практически исключает дублирование терминов и их толкований. Данная статья посвящена попытке создания удобной и эффективной системы упорядочивания знаний в области программирования, которая может пригодиться не только в работе с My Visual Database (MVDB), но и с другими системами, использующими объектно-ориентированный подход (ООП).

Лирическое отступление

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

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

Конечно, ещё существует живопись – картины великих мастеров способны передавать концепции в двумерном представлении, а детальная проработка позволяет при считывании “масштабировать” информацию, приближаясь или удаляясь от картины. Но язык изобразительного искусства ориентирован скорее на духовное и эстетическое просвещение. И хотя для изложения технических задач люди придумали чертежи, работать с ними сложно и возможности их также ограничены.

С появлением компьютеров появились новые возможности: гипертекст, мультимедиа, интерактивность. Двумерность канала передачи стала полноценной, продолжаются попытки расширить его как за счет одновременного использования других сенсорных каналов (звук, тактильные ощущения), так и добавлением трёхмерности в создаваемые изображения, а также эксперименты в области дополненной реальности. 

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

Но все же позвольте представить систему для накопления специальных знаний в области объектно-ориентированного подхода, который успешно реализован во многих языках программирования и средах разработки, в том числе – в MVDB.

Базис и надстройка

Эти понятия марксисткой теории вполне можно применить в нашем случае, но с поправкой, что надстройка (среда разработки MVDB) в нашем случае никак не влияет на базис (RAD Delphi и язык ObjectPascal).

По неофициальным сведениям, MVDB собрана в Delphi XE3 с использованием как стандартных компонентов, входящих в Delphi, так и ряда дополнительных, которые обеспечивают создание отчетов, отображение табличных данных и картографических данных Google. Несмотря на  довольно скромную панель компонентов редактора форм MVDB, “на борту авиалайнера” находится ещё около 170 (!) классов, том числе десятки классов визуальных компонентов, которые можно использовать в создаваемых приложениях с помощью скриптов. Вспомнился старый анекдот.  

Закончилась посадка на суперлайнер ИЛ-2086. В салон выходит стюардесса:
" Дамы и господа, для того чтобы помочь вам скоротать время полета, на борту нашего лайнера имеются библиотека, кинозал, три бара, ресторан, бассейн и два теннисных корта. А теперь я попрошу вас пристегнуть ремни безопасности, потому что сейчас вместе со всей этой
фигней мы попытаемся взлететь!"

Размер исполняемого модуля прикладной программы, создаваемой MVDB, составляет 20 092 928 байт (версия 6.6 beta) и не зависит от сложности проекта, используемых визуальных и невизуальных компонентов. Это плата за портабельность: все библиотеки, которые могут потребоваться для работы, находится внутри исполняемого модуля. Сам проект представлен файлом описания форм пользовательского интерфейса (forms.xml), файлом скомпилированных скриптов (script.dcu) и другими вспомогательными файлами, необходимыми для подключения к базе данных, создания отчетов и отображения графического контента.

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

А вот для использования всей мощи встроенного языка программирования и доступных классов потребуется хорошее знание принципов ООП и языка программирования и среды разработки Delphi. Главная проблема в том, что данная среда (базис) продолжает развиваться, в отличии от надстройки, развитие которой застопорилось около года назад, а информация, представленная на официальных сайтах Embarcadero, отражает современное развитие иерархии классов и компонент, доступных в последней версии Delphi. Поэтому часть информации о компонентах и классах, которые спрятаны под капотом MVDB, теперь нужно разыскивать в архивах сайтов, посвященных разработке ПО, различных форумах и блогах. 

Особняком стоит вопрос создания отчетов, так как FastReport до сих пор продается как отдельная подсистема и используется в различных программах. Проект имеет собственную документацию, в том числе на русском языке. Но у неё та же проблема, что была описана выше – линейная подача материала, который местами довольно сложен.

Class Explorer 

Разрабатываемая информационная система будет состоять из двух основных блоков: навигации и отображения.

Навигация

Цель системы навигации – это организация поиска нужных сведений и передача информации о структуре связей и зависимостей между отдельными описываемыми сущностями. Навигация базируется на структуре хранимой информации.

Поиск

Поиск будет сквозным по всем сущностям, хранящимся в справочной системе: классам, типам, методам, свойствам, событиям и т.д. Результат будет отображаться в списке с кратким описанием, при клике по которому отобразится подробное описание (пример использования).

Отображение

Для целей отображения подойдет компонент TdbRichEdit – редактор текста с богатыми возможностями добавления данных в различных форматах представления (изображения, таблицы и т.д.)

Схема данных

Таблицы с основными сущностями имеют поля name (название) и description (краткое описание), а также ссылку на таблицу example, в которой хранятся подробные описания и примеры использования. Такая схема удобна, так как некоторые свойства и методы будут повторяться у разных классов и нет нужды для каждого из них делать отдельное подробное описание. В то же время иногда хочется сделать конкретный пример для отдельного класса. Предложенная схема позволяет гибко подходить к вопросу формирования справочного контента и его объемам.

Так как в My Visual Database нет четкой поддержки наследования на уровне скриптов (то есть, наличие свойства или метода у базового класса не означает его доступность в скриптах как самого класса, так и дочерних классов), то никаких механизмов хранения и получения унаследованных методов, свойств и событий в данной программе не будет.

Классы и типы

Класс является эволюционным продолжением типа, поэтому логично для их хранения использовать одну и ту же таблицу classType. Но для удобства фильтрации данных я добавил логическое поле isType, в котором буду явно указывать, относится ли данная запись к типам. Это пригодится при визуализации списка типов и списка классов. А вот для дерева классов потребуется поле parentID.

Функции и процедуры

Процедуры, функции, а также методы классов хранятся в таблице funcProc. Чтобы понять, к какому классу относится данный метод, используется поле-ссылка id_classType. Если оно пустое, то значит данная запись относится к общедоступным функциям и процедурам, доступным в скриптах MVDB. Этот факт используется при фильтрации данных для отображения списка методов класса и списка функций. Второе поле-ссылка id_classType1 необходимо, чтобы указать тип возвращаемого результата функции. Если оно пустое, то мы имеем дело с процедурой (для удобства отображения типа результата функции добавлено вычисляемое поле ResultType). А вот для отображения дерева функций (для разделения функций на категории) потребовались ещё два поля: parentID для хранения ссылки на родительский элемент и isGroup для хранения признака, что элемент является названием группы (группа отображается в дереве, но не отображается в списке функций).

Свойства и глобальные переменные

Для хранения этих сущностей предназначена таблица property. Для свойств необходимо заполнить поле-ссылку id_classType, если она пустая, то запись хранит сведение о глобальной переменной (их всего три, но не стоит о них забывать). Для указания типа свойства служит поле id_classType1 (для удобства отображения типа свойства добавлено вычисляемое поле ResultType).

События

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

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

Параметры процедур и функций

Для хранения списка параметров предназначена таблица funcProcParam. Можно было бы обойтись и без неё, указывая параметры в полном описании описании функции, но в целях полноты структурного представления я решил добавить отдельную таблицу. Кроме связи с функцией id_funcProc, в поле orderNum хранится порядковый номер параметра.

Поименованные константы

По факту большинство типов является целочисленными, но для повышения надежности написания кода в языке Паскаль применяется строгая типизация, а для удобства работы со значениями типов – именованные константы. Для их хранения в программе предусмотрена таблица typeConst.

Задачи

На практике разработчик решает конкретные задачи, связанные с отображением или поиском данных. Именно поэтому нам необходима таблица task, где в иерархической форме будут храниться типовые задачи, которые обычно отвечают на вопрос “как”: как добавить пункт в главном меню, как раскрасить таблицу в разные цвета, как автоматически делать резервные копии базы данных и так далее. Для создания иерархической структуры понадобится поле parentID, а для того, чтобы отличать категории от самих задач, – поле isGroup

Примеры

Один и тот же пример может подойти для иллюстрации различных классов, процедур или решаемых задач. Все они хранятся в таблице example, в которой всего два поля: caption (заголовок) и detail (содержание). В данном поле будет хранится данные, создаваемые компонентом TdbRichEdit, что позволит включать в примеры исходные тексты с форматированием или изображения.

Экранные формы

У приложения будет главная форма и формы редактирования справочников.

Структура главной формы получается довольно сложной, поэтому я решил использовать технику динамической сборки главной формы: на главной форме располагаются многостраничные вкладки, а также панель с кнопками редактирования и строкой поиска. А табличные и древовидные представления находятся на отдельных формах, которые соединяются с главной формой в момент запуска приложения (в дальнейшем я планирую создавать их с помощью скриптов, отказавшись от визуального редактора – они простые и однотипные). Для этого я буду использовать процедуру Form_ShowOnWinControl().

Скрипты

Для удобства потребуется добавить сплиттеры – элементы, с помощью которых можно менять размеры визуальных компонентов в работающем приложении. Стандартное главное меню мне не понадобится, поэтому его нужно спрятать. Для всего этого пригодится процедура InitForm.

procedure InitForm;
var
  tmpSplitter: TSplitter;
begin
  // убрать главное меню
  frmMain.Menu := nil;
  frmMain.DoubleBuffered := True;
  // добавляем сплиттеры и выравнивание
  // классы
  frmMain.pgcClassView.align := alLeft;
  tmpSplitter := TSplitter.Create(frmMain);
  tmpSplitter.Parent := frmMain.tshClass;
  tmpSplitter.Left := frmMain.pgcClassView.Width + 1;
  tmpSplitter.Width := 7;
  tmpSplitter.align := alLeft;
  frmMain.pgcClass.align := alClient;
  // типы
  frmMain.pgcTypeView.align := alLeft;
  tmpSplitter := TSplitter.Create(frmMain);
  tmpSplitter.Parent := frmMain.tshType;
  tmpSplitter.Left := frmMain.pgcTypeView.Width + 1;
  tmpSplitter.Width := 7;
  tmpSplitter.align := alLeft;
  frmMain.pgcType.align := alClient;
  // функции
  frmMain.pgcFunctionView.align := alLeft;
  tmpSplitter := TSplitter.Create(frmMain);
  tmpSplitter.Parent := frmMain.tshFunction;
  tmpSplitter.Left := frmMain.pgcFunctionView.Width + 1;
  tmpSplitter.Width := 7;
  tmpSplitter.align := alLeft;
  frmMain.pgcFunction.align := alClient;
  // переменные
  frmMain.pgcVariableView.align := alClient;
  // задачи
  frmMain.pgcTaskView.align := alClient;
  // методы
  frmMain.panMethod.align := alTop;
  tmpSplitter := TSplitter.Create(frmMain);
  tmpSplitter.Parent := frmMain.tshMethod;
  tmpSplitter.Top := frmMain.panMethod.Height + 1;
  tmpSplitter.Height := 7;
  tmpSplitter.align := alTop;
  frmMain.pgcMethodParamView.align := alClient;
 
  Form_ShowOnWinControl( frmClassTree, frmMain.tshClassTree );
  Form_ShowOnWinControl( frmClassList, frmMain.tshClassList );
  Form_ShowOnWinControl( frmTypeList, frmMain.tshTypeList);
  Form_ShowOnWinControl( frmFunctionTree, frmMain.tshFunctionTree );
  Form_ShowOnWinControl( frmFunctionList, frmMain.tshFunctionList );
  Form_ShowOnWinControl( frmVariableList, frmMain.tshVariableList );
  Form_ShowOnWinControl( frmTaskTree, frmMain.tshTaskTree );
  Form_ShowOnWinControl( frmTaskList, frmMain.tshTaskList );
  Form_ShowOnWinControl( frmProperty, frmMain.tshProperty );
  Form_ShowOnWinControl( frmMethod, frmMain.panMethod );
  Form_ShowOnWinControl( frmEvent, frmMain.tshEvent );
  Form_ShowOnWinControl( frmMethodParamList, frmMain.tshMethodParamList );
  Form_ShowOnWinControl( frmTypeConst, frmMain.tshTypeConst );
  Form_ShowOnWinControl( frmFunctionParam, frmMain.tshFunctionParam );
  Form_ShowOnWinControl( frmSearchResult, frmMain.tshSearchResult );
  Form_ShowOnWinControl( frmExampleView, frmMain.tshExample );
  //
  Tree_LoadCollapseList( frmFunctionTree.trvMain );
  Tree_LoadCollapseList( frmClassTree.trvMain );
  Tree_LoadCollapseList( frmTaskTree.trvMain );
end;

Процедура Form_ShowOnWinControl() “пристёгивает” форму к любому наследнику TWinControl, в нашем случае это будут вкладки многостраничника и панели. Также в ней происходит вызов универсальной процедуры Form_UpdateData() для обновления данных на форме .

procedure Form_ShowOnWinControl(AForm: TAForm; AControl: TWinControl;);
// отображение формы на контейнере
begin
  // пристёгивать форму, если она ещё не пристёгнута
  if AForm.Parent = nil then
  begin
    // подготавливаем форму
    AForm.borderStyle := bsNone;
    AForm.Align := alClient;
    // накидываем её на панель
    AForm.Parent := AControl;
    SetParent(AForm.Handle, AControl.Handle);
  end;
  // сделать форму видимой
  AForm.Visible := True;
  // обновить данные на форме
  Form_UpdateData(AForm);
  AForm.BringToFront;
end;

Обновление данных основано на принципах соглашений по наименованию элементов, согласно которому на форме может быть кнопка для обновления данных с названием btnUpdate. Если её нет, то производится непосредственное обновление табличного представления tgrMain или дерева trvMain, причем для дерева делается корректировка сортировки элементов, установкой свойства dbCustomOrderBy. Это позволит упорядочить узлы дерева внутри ветки по алфавиту.

procedure Form_UpdateData(AForm: TAForm);
// обновить табличные данные на форме
var
  tmpButton: TdbButton;
  tmpGrid: TdbStringGridEx;
  tmpTree: TdbTreeView;
begin
  FindC(AForm, 'btnUpdate', tmpButton, False);
  if tmpButton <> nil then
    tmpButton.Click
  else
  begin
    FindC(AForm, 'tgrMain', tmpGrid, False);
    if tmpGrid <> nil then
    begin
      tmpGrid.dbUpdate;
    end
    else
    begin
      FindC(AForm, 'trvMain', tmpTree, False);
      if tmpTree <> nil then
      begin
        // добавляем вторичную сортировку дерева по названию
        tmpTree.dbCustomOrderBy := 'ParentID,name';
        tmpTree.dbUpdate; //
      end
    end
  end;
end;

Продолжение: Лечение копипаста

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *