В реализации метода 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 эти недоработки будут устранены.
Oracle10g Rel.1 (10.1) PL/SQL User's Guide and Reference
Oracle10g Rel.1 (10.1) Application Developer's Guide - Object-Relational Features