Объектно-ориентированный подход
Делегирование
Сервисы PFC
Общая структура PFC
Построение PFC-приложений

Одной из основных отличительных черт PowerBuilder как средства разработки является наличие хорошо продуманной библиотеки классов - PowerBuilder Foundation Classes (PFC). Однако, при первой попытке использовать эту библиотеку для разработки приложений программисты встречаются с многими трудностями. Цель данной статьи состоит в том, чтобы показать читателю основные концепции, лежащие в основе PFC, а также продемонстрировать технику ее использования.

Возможно, первый вопрос, который вы зададите себе, будет: зачем использовать PFC? Конечно же, вы можете разрабатывать приложения без помощи какой-либо библиотеки классов или иного средства, пользуясь только штатными возможностями PowerBuilder. Тем не менее, использование PFC может значительно ускорить разработку и улучшить функциональность приложения, поскольку PFC предоставляет готовые классы и события, для создания и отладки которых потрачено значительное время многих талантливых программистов. Но для того чтобы научиться использовать классы PFC, нужно понять ее базовые концепции.

Объектно-ориентированный подход

Как известно, PowerBuilder не является истинным объектно-ориентированным инструментарием: ряд его сущностей отходит от классических концепций объектно-ориентированного подхода. Например, не являются истинными классами Function, Structure, DataWindow и Query. Для функций нет возможности задать атрибуты, для структур - методы, для DataWindow и Query не могут быть определены методы и наследование. Application рассматривается как "корневой" класс для приложения, однако к нему также не может быть применено понятие наследования. Истинными классами являются "только" Window, Menu и User Object, но этого "только" с лихвой хватает для построения самых сложных приложений.

Как известно, между классами существуют следующие отношения:

  • отношение использования
  • отношение наполнения
  • отношение наследования
  • отношение типа метакласс.

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

Остановимся подробней на наиболее важных отношениях. Ассоциирование - это такое отношение между двумя или более классами (объектами), которое описывает группу связей с общей структурой и семантикой. Это отношение можно охарактеризовать словами "использует" ("uses a") и "используется" ("used by"). К нему также применимы понятия "клиент" и "сервер", где сервер - это класс (объект), предоставляющий некоторую функциональность, а клиент - класс (объект), этой функциональностью пользующийся. Агрегирование - это отношение между двумя или более классами (объектами), когда один класс (объект) подчинен другому и используется только этим классом (объектом). Это отношение можно охарактеризовать словами "имеет" ("has a") и "является частью" ("part of"). К нему применимы понятия "целое" и "часть". Наследование - это отношение между двумя классами, которое состоит из класса-предка и класса-наследника. Этот тип отношений, представляющий собой взаимосвязь классов в терминах обобщение-специализация, может быть охарактеризован словами "представляет собой" ("is a") и "является видом" ("kind of"). Если ранжировать эти отношения по степени ограничений, налагаемых на классы, то наиболее свободным будет отношение ассоциирования, в котором сервер может вообще ничего не знать о клиенте, предоставляя в пользование свою функциональность любому, кто в ней нуждается. Наиболее тесные ограничения накладывает наследование, поскольку в этом случае требуется тесная взаимоувязка классов по атрибутам, структурам, типам и т.п.

В первые годы развития объектно-ориентированной методологии наиболее важными считались концепции наследования, инкапсуляции и полиморфизма, однако в последнее время на первый план "выступили" концепции ассоциирования и агрегирования. Появился новый термин - делегирование, основанный на этих концепциях. Суть его состоит в возможности использования объектами функциональности, заключенной во внешних объектах. Благодаря такому подходу появляется возможность построения компонентной архитектуры, при которой каждый компонент или группа компонентов отвечает за предоставление клиентам определенных сервисов. Сервис - это просто группа связанных процессов, производящих некоторую обработку, например, поиск, изменение размера элемента, кэширование и т.д. Таким образом, делегирование разбивает основную иерархическую структуру классов на несколько отдельных иерархий, значительно уменьшая потребности в оперативной памяти, поскольку разработчик имеет возможность загружать каждый из сервисных классов по мере необходимости и не держать в памяти громоздкую иерархическую структуру. Как вы уже догадались, PFC активно "впитала" эти технологические новшества, поэтому наряду со "стандартным" объектно-ориентированным подходом (наследование применяется для реализации иерархии окон, меню и пользовательских классов, инкапсуляция используется для изоляции данных и кода класса, полиморфизм обеспечивает "одноименность" в наименованиях функций в пределах класса, иерархии классов или среди несвязанных классов) повсеместно применяется принцип делегирования, обеспечивающий наиболее легковесные связи между классами. Можно утверждать, что делегирование явилось основополагающим принципом при проектировании архитектуры всей библиотеки.

Делегирование

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

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

Тем не менее, мы должны отвергнуть это решение, поскольку очевидно, что загрузка в оперативную память всей структуры класса потребует большого количества ресурсов и, скорее всего, снизит производительность. Каждый класс, наследуемый от данного предка, "вынужден" загружать в память потенциально неиспользуемый багаж. Кроме того, для внесения изменений в данный класс потребуется тщательно проверять эти изменения на корректность и непротиворечивость. Более правильное решение состоит в том, чтобы убрать из предка всю специфическую функциональность, оставив в нем только общие данные и методы.

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

Давайте теперь посмотрим, как данная проблема может быть решена с помощью делегирования. В этом случае мы должны рассматривать выделение строк и фильтрацию как внешние сервисы для нашего класса DataWindow. Такую функциональность мы можем выполнить в виде отдельных классов, которым будет "делегирована" часть действий класса DataWindow. Именно так это и реализовано в PFC: существует класс u_dw, который делегирует функциональность, связанную с выделением строк и фильтрацией, классам n_cst_dwsrv_rowselection и n_cst_dwsrv_filter, соответственно.

Такая архитектура имеет много преимуществ. В нашем распоряжении - достаточно легковесные классы, инкапсулирующие функциональность, которая может использоваться в любом приложении. Объект-клиент запрашивает требуемые сервисы путем перенаправления потока событий к объектам-серверам или вызова их соответствующих функций. Таким образом, создатели PFC разбили плоское дерево иерархии классов на несколько объектных иерархий и получили возможность использовать требуемую функциональность по запросу без необходимости загрузки всего предка.

Но отсюда не следует, что наследование больше не используется. Напротив, наследование применяется для того, чтобы расширить функциональность компонента, добавив или изменив некоторые возможности. Поэтому в PFC делегирование и наследование используются вместе, дополняя друг друга. Тем не менее, разработчику каждый раз при создании своих классов необходимо делать выбор в пользу делегирования или наследования. Ведь даже когда между некоторыми сущностями имеется зависимость вида "представляет собой" - "является видом" ("is a" - "kind of"), т.е. когда одна сущность является специализацией другой, то и здесь во многих случаях такую зависимость удобней моделировать с помощью делегирования. Однако, делегирование имеет и обратную сторону. За все нужно платить. И в данном случае плата представляет собой повышенную трудоемкость при разбиении одной иерархии классов на несколько отдельных иерархий, а также может вызвать некоторое уменьшение эффективности приложения из-за постоянного перенаправления потока событий. Но все тяжело только в первый раз! Затратив определенные усилия, вы получите легко управляемую систему, которую очень просто сопровождать и модифицировать. И при этом не требуется знать детали реализации классов PFC - только их интерфейс!

Итак, вся библиотека PFC представляет собой компоненты, которые имеют возможность предоставлять "пользовательским" классам необходимые сервисы, используя принцип делегирования. Для расширения функциональности компонентов активно используется наследование, равно как и все остальные принципы объектно-ориентированной методологии. Рассмотрим теперь, какие сервисы предлагают классы PFC.

Сервисы PFC

Практически все компоненты PFC реализованы в виде настраиваемых пользовательских классов (Custom Class User Object), которые, как мы помним, представляют собой истинные объектные сущности. Используются также классы Window, Menu и Custom Visual User Object. PFC предоставляет следующие основные категории сервисов:

  • сервисы приложения
  • оконные сервисы
  • сервисы меню
  • сервисы DataWindow,

а также большое число сервисов, реализующих утилиты:

  • файловые сервисы (File)
  • сервисы INI (INI)
  • платформо-зависимые сервисы (Platform)
  • сервисы даты и времени (DateTime)
  • строковые сервисы (String)
  • сервисы преобразования (Conversion)
  • сервисы разбора предложений SQL (SQL Parsing)
  • числовые сервисы (Numerical)
  • сервисы выбора (Selection).

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

Итак, вначале - о сервисах приложения.

Сервисы приложения включают в себя следующие:

  • сервис кэширования DataWindow (DataWindow Caching)
  • сервис регистрации ошибок (Error)
  • сервис авторизации (Security)
  • сервис регистрации транзакций (Transaction Registration)
  • сервис отладки SQL-предложений (SQL Spy Debug)

Сервис DataWindow Caching позволяет сохранять данные, используемые в различных местах приложения, в кэше, устраняя необходимость чтения неизмененных данных с диска.

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

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

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

Сервис SQL Spy Debug обеспечивает средства для отладки предложений SQL во время разработки приложения. Он включает в себя возможность записи в журнал всех предложений SQL в DataWindow и предложений EXEC IMMEDIATE с показом содержимого и возможностью изменения SQL перед выполнением. Следует отметить, что в большинстве случаев вполне достаточно возможностей, предоставляемых событием SQLPreview в элементе управления DataWindow.

Теперь перейдем к оконным сервисам. Они предоставляют функциональность на уровне окна и включают в себя следующие сервисы:

  • базовый сервис (Basic)
  • сервис изменения размеров (Resize)
  • сервис состояния (StatusBar)
  • сервис управления дочерними окнами (Sheet Management)
  • сервис конфигурации (Preference).

Базовый сервис обеспечивает возможность центрирования окна вне зависимости от разрешающей способности дисплея.

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

Сервис StatusBar обладает возможностью отображать системную информацию в строке состояния. Эта информация может включать в себя состояние памяти (GDI, free, user), а также дату и время.

Сервис Sheet Management обеспечивает управление большим количеством дочерних окон с возможностью расположения их в требуемом порядке, восстановления последних размеров, а также управление массивом обработчиков (handles) окон. Также должен отметить, что данный сервис применяется не часто.

Сервис Preference предоставляет возможность сохранять и восстанавливать состояние окна в/из INI-файле или системном реестре Windows. Этот сервис в совокупности с сервисом Resize являются наиболее полезными в данной категории.

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

Наиболее объемная - а вместе с тем и полезная - категория сервисов DataWindow. Эта категория включает в себя следующие сервисы:

  • базовый сервис (Basic)
  • сервис связанных DataWindow (Linkage)
  • сервис обновления нескольких таблиц (Multi-table Updates)
  • сервис режима запроса (Query Mode)
  • сервис печати (Reporting)
  • сервис предварительного просмотра печати (Print Preview)
  • сервис управления строками (Row Management)
  • сервис выделения строк (Row Selection)
  • сервис сортировки (Sort)
  • сервис фильтрации (Filter)
  • сервис поиска/замены (Find/Replace)
  • сервис поиска в DropDownDataWindow (DropDownDataWindow Search)
  • сервис обязательных полей (Required Column).

Начнем с базового сервиса. Он предоставляет возможность получения базовой информации о DataWindow и его содержимом и включает в себя такие черты как: удобная "надстройка" над функциями Describe и Modify, определение аргументов запроса для DataWindow или DDDW, задание вида наименования столбцов в диалоговых окнах сортировки, фильтрации и режима запроса, обновление DDDW, получение списка всех объектов в DataWindow и многое другое. Заметим, что классы, реализующие базовый сервис, создаются автоматически при "разрешении" любого из ниже описываемых сервисов.

Сервис Linkage позволяет легко координировать обработку данных в двух или более DataWindow, связанных отношением Master/ Detail, и включает в себя следующие возможности: задание столбцов, через которые осуществляется связывание, а также способа отображения данных (выборка из базы данных, фильтрация, скроллинг), обновление данных по всей цепочке связанных DataWindow, изменение последовательности обновления DataWindow, инициирование пользовательских событий в каждом из DataWindow, контроль ошибок валидации во всей цепочке и т.п.

Сервис Multi-table Updates предназначен для обновления DataWindow, объединяющего более одной таблицы. Он позволяет легко описать таблицы и поля, требующие обновления, а также выполнить само обновление "за один присест". К сожалению, в нем отсутствует возможность ограничивать возможные операции над таблицами - например, запретить вставку, разрешив только модификацию. Но, в целом, это очень полезный сервис.

Сервис Query Mode расширяет управление режимом запроса (Query Mode), включая возможности сохранять запросы и использовать их в дальнейшем.

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

"Родственный" предыдущему сервис Print Preview позволяет быстро переключаться в режим предварительного просмотра печати и обратно, подсчитывать количество страниц, использовать коэффициент увеличения и т.п. Эти два сервиса удобно использовать вместе.

Сервис Row Management реализует расширенное управление вставкой и удалением строк в DataWindow, включая возможность в диалоговом режиме восстановить любое количество удаленных строк.

Сервис Row Selection предоставляет поддержку различных способов выделения строк: выделение единственной строки, выделение нескольких строк, расширенный режим выделения в стиле Windows 95 (используя мышь и клавиши CTRL/SHIFT), - а также возможность изменять выделение на противоположное.

Сервис Sort предназначен для обеспечения расширенных способов сортировки (щелчком мыши по заголовкам столбцов, вызовом одного из четырех видов диалоговых окон), а также возможности сортировки по отображаемым значениям столбцов, в противоположность реальным данным (кодам).

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

Сервис Find/Replace обеспечивает возможность поиска данных любых типов в любых полях, опционально включая возможность замены, с использованием диалогового окна. Имеется также поддержка стиля представления RichText.

Очень интересные возможности предоставляет сервис DropDownDataWindow Search. Он позволяет автоматически скролировать значения DDDW и заполнять поле по мере ввода символов пользователем. Такое свойство облегчает построение дружественного интерфейса пользователя.

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

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

Общая структура PFC

Все классы PFC разделены на два уровня - базовый уровень (уровень предков) и расширенный уровень (уровень потомков). Смысл такого разделения состоит в том, что вся функциональность заключена в базовом уровне, расширенный же уровень представляет собой набор неизмененных потомков базовых классов. Разработчики PowerBuilder гарантируют, что все модификации будут касаться только базового уровня, поэтому изменения вносить рекомендуется только в расширенный уровень. Для каждого из двух уровней имеется свой набор библиотек PowerBuilder: классы базового уровня находятся в библиотеках PFCAPSRV, PFCDWSRV, PFCMAIN и PFCWNSRV и имеют префикс 'pfc_', классы расширенного уровня находятся в библиотеках PFEAPSRV, PFEDWSRV, PFEMAIN и PFEWNSRV. Итого - восемь библиотек. Библиотеки PFCAPSRV/PFEAPSRV обеспечивают управление приложением и классы глобальных сервисов. В библиотеках PFCMAIN/PFEMAIN находятся классы, предоставляющие базовые сервисы для разработки приложения. Библиотеки PFCDWSRV/PFEDWSRV охватывают сервисы DataWindow. Библиотеки PFCWNSRV/ PFEWNSRV "отвечают" за оконные сервисы. Поэтому все эти библиотеки должны быть включены в список Libraries.

Среди всех классов PFC можно выделить четыре основных класса: pfc_n_tr, pfc_n_cst_appmanager, pfc_w_master и pfc_u_dw.

pfc_n_tr является предком всех классов, осуществляющих обработку транзакций

pfc_n_cst_appmanager - предок классов, осуществляющих управление приложением

pfc_w_master - предок всех окон в PFC

pfc_u_dw - предок визуальных пользовательских классов DataWindow.

Прежде чем использовать какой-либо класс PFC, следует внимательно ознакомиться с его событиями и функциями. Хорошего понимания структуры классов можно добиться изучением примеров, входящих в поставку PowerBuilder.

Построение PFC-приложений

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

1. Включить все библиотеки PFC в список Libraries диалогового окна Application мастерской Application в порядке, соответствующем.

2. Декларировать глобальную переменную gnv_app (обязательно с таким именем, поскольку функции PFC предполагают наличие переменной менеджера приложения именно с таким именем), имеющую тип, например, n_cst_appmanager (или его наследника), создать ее в событии Open приложения и уничтожить в событии Close.

Событие Open приложения:

gnv_app = CREATE  n_cst_appmanager 
//создание объекта
gnv_app.Event pfc_Open(commandline) 
//инициирование события pfc_Open класса
Событие Close приложения:
gnv_app.Event pfc_Close()
//инициирование события pfc_Close класса
DESTROY gnv_app//уничтожение объекта

3. Написать код в событиях Constructor и pfc_Open класса n_cst_appmanager (или его наследника) для инициализации своего приложения.

Событие Constructor класса n_cst_appmanager (или его наследника):

of_SetAppInifile('glenmos.ini') 
//Задание INI-файла
of_SetHelpFile('glenmos.hlp')  
//Задание Help-файла
of_SetLogo('glenmos.bmp')  
//Задание файла с логотипом
of_SetCopyright('Copyright (c) 1997, Glencore Int.')
of_SetVersion('Version 1.0')
iapp_object.DisplayName = 'Test of PFC libraries' 
//Задание имени приложения
Событие pfc_Open класса n_cst_appmanager (или его наследника):
of_LogonDlg() 
//Вызов окна с запросом коннекта
of_Splash() //Вызов окна с заставкой
Open(w_pens_frame) 
//Вызов главного окна приложения

Это - все! А дальше - везде включать классы PFC вместо стандартных элементов управления PowerBuilder и настраивать реакцию на события. Но об этом - в следующих статьях.


Евгений Шадрин системный аналитик, Glencore International AG e-mail: eug@glencore.ru тел. (095) 258-2100