Объектно-ориентированный PL/SQL: проблемы и методы их решенияИсточник: ORACLE MAGAZINE Русское издание Игорь Мельников
Оглавление
ВведениеМногие из нас с появлением Oracle9i Database и далее Oracle10g Database начали активно разрабатывать приложения с помощью объектно-ориентированного PL/SQL. Однако вскоре выяснилось, что корпорация Oracle не полностью реализовала возможности присущие объектно-ориентированным языкам. В результате многие разработчики приложений на Oracle Database "охладели" к объектным возможностям PL/SQL. В предлагаемой вашему вниманию статье предлагается ряд решений проблем, с которыми сталкиваются разработчики. Я уверен, что Oracle9i PL/SQL позволяет реализовывать развитую объектную модель, и, надеюсь, мое мнение разделит читатель. Каждый раздел статьи сопровождается исходными текстами скриптов, демонстрирующими сответствующий подход. Все скрипты запускались и проверялись с помощью последней доступной на текущий момент версии Oracle10g Database - 10.1.0.2 Скрипты тестировались на следующей версии: Oracle 10.1.0.2 Enterprise Edition for linux x86 (Intel Xeon) Вызов переопределенного метода в типе-потомкеС этой проблемой PL/SQL-программисты сталкиваются наиболее часто. Проблема связана с тем, что в PL/SQL отсутствует синтаксическая конструкция для вызова метода типа-предка. В случае порождения нового объектного типа от родительского, виртуальный метод переопределеяется, далее в этом порожденном типе нам необходимо вызвать данный метод типа-предка. Например: пусть у нас есть класс t_ParentType в котором определен метод getName: ------------------------------------------------------------ --спецификация объектного типа t_ParentType: - ------------------------------------------------------------ create or replace type t_ParentType as object ( v_Field1 varchar2(32), member function getName return varchar2 ) not final; ------------------------------------------------------------ --тело объектного типа t_ParentType: - ------------------------------------------------------------ create or replace type body t_ParentType as member function getName return varchar2 is begin return self.v_Field1; end; end; Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType метод getName является виртуальным и переопределен. Для этого использовано ключевое слово OVERRIDING: ------------------------------------------------------------ --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --Внимание: метод getName переопределен - ------------------------------------------------------------ create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), overriding member function getName return varchar2 ) not final; В реализации метода getName попытаемся вызвать унаследованный метод getName (объектного типа t_ParentType) ------------------------------------------------------------ --Тело объектного типа t_ChildType, - --в методе getName необходимо вызвать унаследованный метод - ------------------------------------------------------------ create or replace type body t_ChildType is overriding member function getName return varchar2 is begin return (???) getName // ' ' // v_Field2; -- как вызвать -- метод предка ??? end; end; Таким образом, выясняется, что в PL/SQL нет синтаксической конструкции, для того чтобы сослаться на метод типа-предка. В объектно-ориентированных языках для этого существуют специальные языковые конструкции. В Java это ключевое слово super (супер-класс), в Object Pascal - Inherited. Данный механизм обеспечивает доступ к унаследованной логике и устраняет избыточность кода. Документация по языку PL/SQL (Oracle10g Rel.1 PL/SQL User's Guide and Reference, Oracle10g Rel.1 Application Developer's Guide - Object-Relational Features) хранит по этому поводу молчание. Для решения этой проблемы предлагается следующий алгоритм:
Модифицируем исходный текст для реализации этого подхода, добавив в родительский тип копирующий конструктор: ------------------------------------------------------------ --спецификация объектного типа t_ParentType, - --добавлен копирующий конструктор - ------------------------------------------------------------ create or replace type t_ParentType as object ( v_Field1 varchar2(32), --копирующий конструктор: constructor function t_ParentType(v_pObject in out nocopy t_ParentType) return self as result, member function getName(self in out nocopy t_ParentType) return varchar2 ) not final; ------------------------------------------------------------ --тело объектного типа t_ParentType - ------------------------------------------------------------ create or replace type body t_ParentType is constructor function t_ParentType(v_pObject in out nocopy t_ParentType) return self as result is begin self.v_Field1 := v_pObject.v_Field1; return; end; member function getName(self in out nocopy t_ParentType) return varchar2 is begin return self.v_Field1; end; end; В типе-потомке нам также будет необходим метод присваивания, который будет копировать все поля переменной экземпляра типа в текущий экземпляр, - назовем его assign. Далее добавим функцию inherited_getName, которая будет реализовывать алгоритм вызова функции getName родительского типа t_ParentType. Фактически метод inherited_getName представляет собой оболочку для метода getName типа-предка t_ParentType. ------------------------------------------------------------ --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --Добавлен метод присваивания - assign - ------------------------------------------------------------ create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pField1 varchar2, v_pField2 varchar2) return self as result, --метод для вызова унаследованного метода getName: member function inherited_getName(self in out nocopy t_ChildType) return varchar2, --метод присваивания: member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ChildType), overriding member function getName(self in out nocopy t_ChildType) return varchar2 ) not final; ------------------------------------------------------------ --Тело объектного типа t_ChildType - ------------------------------------------------------------ create or replace type body t_ChildType is constructor function t_ChildType(v_pField1 varchar2, v_pField2 varchar2) return self as result is begin self.v_Field1 := v_pField1; self.v_Field2 := v_pField2; return; end; member function inherited_getName(self in out nocopy t_ChildType) return varchar2 is v_xInheritedObject t_ParentType; --экземпляр объекта-предка v_xRes varchar2(32); begin -- создаем экземпляр предка с помощью копирующего конструктора v_xInheritedObject := new t_ParentType(self); -- вызываем метод getName класса-предка v_xRes := v_xInheritedObject.getName; -- в общем случае вызов метода предка мог изменить поля self.assign(v_xInheritedObject); -- поэтому необходимо обратно скопировать их в текущий объект (self) return v_xRes; end; ---------------------------------------------------------- -- метод присваивания: - -- просто копируем все поля-объекта источника в текущий - -- экземпляр (self) - ---------------------------------------------------------- member procedure assign(v_pObject in out nocopy t_ChildType) is begin self.v_Field1 := v_pObject.v_Field1; self.v_Field2 := v_pObject.v_Field2; end; ---------------------------------------------------------- -- переопределенный метод getName: - -- через вызов inherited_getName происходит обращение к - -- унаследованному методу getName - ---------------------------------------------------------- overriding member function getName(self in out nocopy t_ChildType) return varchar2 is begin return inherited_getName // '-' // v_Field2; end; end; В заключение, можно заметить, что данная методика применима для иерархий типов произвольного числа уровней. Если же необходимо обеспечить вызов переопределенного метода из произвольного родительского класса, то нужно иметь уникальные имена для методов-оболочек. Например: в следующей иерархии классов: t_ParentType -> t_ChildType -> t_SubChildType для вызова метода произвольного типа-предка можно использовать следующие правило: к имени метода добавляется цифра - номер уровня в иерархии. В этом случае имена методов-оболочек соответственно будут выглядеть следующим образом: getName0->getName1->getName2 Наследование конструкторовОчередная трудность связана с тем, что в PL/SQL не поддерживает прямой вызов унаследованного конструктора. (Проще говоря, конструкторы базового типа не наследуются!). Например: пусть у нас есть класс t_ParentType в котором определен пользовательский (user-defined) конструктор: ---------------------------------------------------------------- --спецификация объектного типа t_ParentType: - ---------------------------------------------------------------- create or replace type t_ParentType as object ( v_Field1 varchar2(32), constructor function t_ParentType(v_pName varchar2) return self as result ) not final; ---------------------------------------------------------------- --тело объектного типа t_ParentType: - ---------------------------------------------------------------- create or replace type body t_ParentType as constructor function t_ParentType(v_pName varchar2) return self as result is begin self.v_Field1 := v_pName; return; end; end; Теперь мы определяем объектный тип t_ChildType, который является наследником t_ParentType. В типе t_ChildType также определен пользовательский конструктор: ---------------------------------------------------------------- --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - ---------------------------------------------------------------- create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result ); В реализации конструктора типа t_ChildType попытаемся вызвать унаследованный конструктор: ---------------------------------------------------------------- --Тело объектного типа t_ChildType - --в конструкторе необходимо вызвать унаследованный конструктор - ---------------------------------------------------------------- create or replace type body t_ChildType is constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result is begin t_ParentType(v_pName => v_pName); self.v_Field2 := v_pDescription; return; end; end; Выясняется, что сделать это не удается: liNE/COL ERROR -------- ----------------------------------------------------------------- 6/5 PLS-00306: wrong number or types of arguments in call to 'T_PARENTTYPE' Итак: как же вызвать конструктор родительского типа, чтобы не дублирвать уже реализованный в нем код ? Предлагается примерно тот же самый метод, что и в предыдущем разделе: создание экземпляра типа-предка, с последующим присвоением его полей полям текущего экземпляра. Для этого нам понадобится метод присвоения assign: ---------------------------------------------------------------- --Спецификация объектного типа t_ChildType, - --который является наследником : t_ParentType - --добавлен метод присваивания assign - ---------------------------------------------------------------- create or replace type t_ChildType under t_ParentType ( v_Field2 varchar2(64), constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result, member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ParentType), member function getName return varchar2 ); ---------------------------------------------------------------- --Тело объектного типа t_ChildType - --в конструкторе вызывается конструктор базового типа - ---------------------------------------------------------------- create or replace type body t_ChildType is constructor function t_ChildType(v_pName varchar2, v_pDescription varchar2) return self as result is --экземпляр объекта-предка v_xInheritedObject t_ParentType; begin --вызов конструктора базового типа v_xInheritedObject := new t_ParentType(v_pName => v_pName); -передача данных текущему экземпляру self.assign(v_xInheritedObject); - self.v_Field2 := v_pDescription; return; end; -------------------------------------------------------------- --метод присваивания экземпляра базового типа текущему - --оьъекту (self) - -------------------------------------------------------------- member procedure assign(self in out nocopy t_ChildType, v_pObject in out nocopy t_ParentType) is begin self.v_Field1 := v_pObject.v_Field1; end; member function getName return varchar2 is begin return self.v_Field1 // ' - ' // self.v_Field2; end; end; Вышеописанная методика демонстрируется в данном примере. Реализация констант-атрибутов типаОбъектно-ориентированное расширение языка PL/SQL поддерживает статические методы типа, однако во многих случаях бывает необходимо использовать статические атрибуты класса, к сожалению PL/SQL не поддерживает такие поля. Нам бы хотелось иметь подобный код: create or replace type t_ParentType as object ( v_Name varchar2(50), static v_Const varchar2(32) := 'Scott Tiger' ); Увы, мы получаем ошибку: ORA-06545: PL/SQL: compilation error - compilation aborted ORA-06550: line 5, column 12: PLS-00103: Encountered the symbol "V_CONST" when expecting one of the following: function procedure Для реализации таких атрибутов можно использовать статический метод, который бы возвращал требуемое значение. Если значение атрибута также имеет объектный тип, то в качестве места хранения значения такого атрибута можно использовать вспомогательный пакет. Для защиты переменной от модификации необходимо поместить её объявление в тело пакета. Следующий листинг реализует данный подход: -------------------------------------------------------------- --Значение данного типа должен иметь атрибут объектного типа - -------------------------------------------------------------- create or replace type t_DictConst as object ( v_Id number(9), v_Name varchar2(50), v_Code varchar2(15), v_Description varchar2(250) ); -------------------------------------------------------------- --Спецификация вспомогательного пакета для типа t_ParentType:- --функция getConst возвращает объект типа t_DictConst - -------------------------------------------------------------- create or replace package serv$ParentType is function getConst return t_DictConst; end; -------------------------------------------------------------- --Тело пакета: объект-константа формируется в процедуре init - -------------------------------------------------------------- create or replace package body serv$ParentType is v_gDictConst t_DictConst; function getConst return t_DictConst is begin return v_gDictConst; end; procedure init is begin v_gDictConst := new t_DictConst(1,'Scott Tiger', '01','Scott Tiger - Oracle demo-user'); end; begin init; end; Следующий объектный тип реализует статический метод, который возвращает объект-константу: -------------------------------------------------------------- --Спецификация объектного типа t_ParentType - --Статический метод возвращает константу - -------------------------------------------------------------- create or replace type t_ParentType as object ( v_Name varchar2(50), static function getConst return t_DictConst ); create or replace type body t_ParentType is static function getConst return t_DictConst is begin return serv$ParentType.getConst; end; end; Вышеописанная методика демонстрируется в данном примере. ЗаключениеМы рассмотрели методы решения наиболее часто встречающихся проблем при использовании объектно-ориентированных возможностей PL/SQL. Конечно, многие проблемы могут быть решены только самими разработчика корпорации Oracle. Например, отсутствие защищенных полей объектного типа (так называемых private-полей), отсутствие поддержки интерфейсов и т.д. Будем надеяться, что в следующих версиях Oracle Database эти недоработки будут устранены. |