(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Объектно-ориентированный PL/SQL: проблемы и методы их решения

Игорь Мельников

Оглавление

Введение

Многие из нас с появлением 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 эти недоработки будут устранены.

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 20.03.2006 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Oracle Database Personal Edition Named User Plus Software Update License & Support
Oracle Database Standard Edition 2 Named User Plus License
Oracle Database Standard Edition 2 Processor License
Oracle Database Personal Edition Named User Plus License
IBM Rational Functional Tester Floating User License
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Новые материалы
Новости мира 3D-ускорителей
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100