Об'єктно-орієнтований 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 PascalInherited. Даний механізм забезпечує доступ до успадкованої логіці і усуває надмірність коду. Документація з мови 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 ці недоробки будуть усунені.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*