Программы, которые существуют в одной области видимости и имеют одинаковые имена, называются перегруженными. PL/SQL поддерживает перегрузку процедур и функций в разделе объявлений блока (именованного или неименованного), в теле и спецификации пакета, а также в объявлении объектного типа. Перегрузка — это очень мощный механизм, и вы должны научиться использовать все его возможности.
В следующем простом примере представлены три перегруженные подпрограммы, определенные в разделе объявлений анонимного блока (а следовательно, являющихся локальными):
DECLARE
/* Первая версия получает параметр DATE. */
FUNCTION value_ok (date_in IN DATE) RETURN BOOLEAN IS
BEGIN
RETURN date_in <= SYSDATE;
END;
/* Вторая версия получает параметр NUMBER. */
FUNCTION value_ok (number_in IN NUMBER) RETURN BOOLEAN IS
BEGIN
RETURN number_in > 0;
END;
/* Третья версия - процедура! */
PROCEDURE value_ok (number_in IN NUMBER) IS
BEGIN
IF number_in > 0 THEN
DBMS_OUTPUT.PUT_LINE (number_in || 'is OK!');
ELSE
DBMS_OUTPUT.PUT_LINE (number_in || 'is not OK!');
END IF;
END;
BEGIN
Когда исполняемое ядро PL/SQL встречает в программе строку вида
IF value_ok (SYSDATE) THEN ...
список фактических параметров сравнивается со списками формальных параметров разных перегруженных модулей. В результате PL/SQL
выполняет код того модуля, для которого указанные списки совпадают.
Перегрузку также называют статическим полиморфизмом. Термином «полиморфизм» обозначается возможность языка определять и использовать несколько программ с одинаковыми именами. Если вызываемый вариант определяется на стадии компиляции — это статический полиморфизм. Если решение принимается во время выполнения, то используется термин «динамический полиморфизм»; этот вид полиморфизма обеспечивается наследованием объектных типов.
Перегрузка способна сильно упростить жизнь как вам, так и другим разработчикам. Она консолидирует интерфейсы вызова многих похожих программ в одно имя модуля. «Информационное бремя» перекладывается с разработчика на программный продукт. Например, вам не придется пытаться запомнить шесть разных имен для программ, добавляющих значения (даты, строки, флаги, числа и т. д.) в разные коллекции. Вместо этого вы просто сообщаете компилятору, что вы хотите добавить значение, и передаете это значение. PL/SQL и перегруженные программы определяют, что вы хотите сделать, и выполняют необходимые действия за вас.
При создании перегруженных подпрограмм вы потратите больше времени на проектирование и реализацию, чем с отдельными, автономными программами. Дополнительное время с лихвой окупится в будущем, потому что и вам, и другим разработчикам будет проще работать с вашим кодом.
Преимущества перегрузки
Перегрузка модулей наиболее уместна в следующих ситуациях:
- Необходимость поддержки разных типов и наборов данных. Если одно и то же действие применяется к разным видам и наборам данных, перегрузка обеспечивает разные варианты активизации этого действия, а не просто позволяет назвать разные действия одним именем.
- Адаптация программы под разные требования. Чтобы ваш код получился по возможности универсальным, можно создать несколько его версий с разными вариантами вызова. Для этого часто требуется выполнять перегрузку процедур и функций. Хорошим признаком ее уместности служит невостребованный, «лишний» код. К примеру, работая с пакетом DBMS_SQL, вы вызываете функцию
DBMS_SQL. EXECUTE
, но при выполнении ею DDL-команд возвращаемое значение игнорируется. Если для этой функции создать перегруженную процедуру, DDL-команду можно выполнить следующим образом:
BEGIN
DBMS_SQL.EXECUTE ('CREATE TABLE xyz ...');
Без применения перегрузки вы должны вызвать функцию
DECLARE
feedback PLS_INTEGER;
BEGIN
feedback := DBMS_SQL.EXECUTE ('CREATE TABLE xyz ...');
а затем игнорировать переменную feedback
.
- Необходимость перегрузки по типу, а не по значению. Самая редкая причина для использования перегрузки. В этом случае версия перегруженной программы выбирается в зависимости от типа данных, а не их значения. Хорошим примером такого рода служит функция
DBMS_SQL.DEFINE_COLUMN
: пакетуDBMS_SQL
необходимо сообщить тип каждого столбца, выбранного при помощи динамического запроса. Для определения числового столбца можно использовать вызовDBMS_SQL.DEFINE_COLUMN
(cur, 1, 1); или вызов
DBMS_SQL.DEFINE_COLUMN (cur, 1, DBMS_UTILITY.GET_TIME);
Конкретное значение роли не играет; мы должны указать «это число», а не привести какое-то конкретное число. Перегрузка обеспечивает элегантное решение этой проблемы. Сейчас мы рассмотрим типичный пример перегрузки, а затем разберемся с ограничениями и рекомендациями по поводу перегрузки.
Поддержка разных комбинаций данных
Перегрузка может использоваться для выполнения одного действия с разными комбинациями данных. Как упоминалось ранее, такая разновидность перегрузки предоставляет не столько общее имя для разных операций, как разные способы вызова одной операции. Возьмем DBMS_OUTPUT.PUT_LINE
: эта встроенная функция может использоваться для вывода значений любых типов данных, которые могут быть явно или неявно преобразованы в строку. Интересно, что в предшествующих версиях Oracle Database (7, 8, 8i, 9i) эта процедура была перегружена, но в Oracle Database 10g и выше она не перегружается! Таким образом, если вам потребуется вывести выражение, которое не может быть неявно преобразовано в строку, вы не сможете вызвать DBMS_OUTPUT. PUT_LINE
и передать это выражение. И что из того, спросите вы? PL/SQL
неявно преобразует числа и даты в строки. Какие еще данные приходится выводить? Для начала — как насчет логических значений? Чтобы вывести значение логического выражения, придется написать команду IF следующего вида:
IF l_student_is_registered
THEN
DBMS_OUTPUT.PUT_LINE ('TRUE');
ELSE
DBMS_OUTPUT.PUT_LINE ('FALSE');
END IF;
Глупо, вам не кажется? И разве это не напрасная потеря времени? К счастью, проблема решается очень просто. Постройте на базе DBMS_OUTPUT. PUT_LINE
свой пакет с множеством перегрузок. Ниже приводится сильно сокращенная версия такого пакета. При желании вы сможете легко расширить ее, как я делаю в процедуре do.pl. Часть спецификации пакета выглядит так:
PACKAGE do
IS
PROCEDURE pl (boolean_in IN BOOLEAN);
/* Вывод строки. */
PROCEDURE pl (char_in IN VARCHAR2);
/* Вывод строки и значения BOOLEAN. */
PROCEDURE pl (
char_in IN VARCHAR2,
boolean_in IN BOOLEAN
);
PROCEDURE pl (xml_in IN SYS.XMLType);
END do;
Пакет просто расширяет DBMS_OUTPUT.PUT_LINE
. При использовании do.pl я могу вывести логическое значение без написания команды IF, как в следующем примере:
DECLARE
v_is_valid BOOLEAN :=
book_info.is_valid_isbn ('5-88888-66');
BEGIN
do.pl (v_is_valid);
Более того, do.pl
можно применять даже к сложным типам данных — таким, как XMLType
:
DECLARE
doc xmltype;
BEGIN
SELECT ea.report
INTO doc
FROM env_analysis ea
WHERE company= 'ACME SILVERPLATING';
do.pl (doc);
END;
Ограничения на использование перегрузки
При выполнении перегрузки необходимо учитывать несколько обстоятельств. Исполнительное ядро PL/SQL
как на этапе компиляции, так и на этапе запуска должно быть в состоянии отличить друг от друга перегруженные версии подпрограммы; ведь оно не может выполнять две подпрограммы одновременно. Так как все перегруженные версии имеют одинаковые имена, исполнительное ядро не может различать их по именам. Для определения того, какую из версий следует выполнить, PL/SQL
использует списки параметров и/или сведения о типе программы (процедура или функция). Все это приводит к тому, что на перегруженные программы накладывается ряд ограничений. У перегруженных программ типы хотя бы одного параметра должны принадлежать к разным семействам. Типы данных INTEGER, REAL, DECIMAL, FLOAT
и т. д. являются числовыми подтипами, а типы CHAR, VARCHAR2 и LONG
— символьными. Если соответствующие параметры отличаются только подтипом, то есть принадлежат к одному супертипу, у PL/SQL
не будет достаточной информации для выбора выполняемой программы.
В следующем разделе описаны некоторые усовершенствования
Oracle10g
(и последующих версий), относящиеся к перегрузке числовых типов.
- Если у перегруженных программ различаются только имена параметров, то при вызове таких программ должно использоваться связывание по имени. Если имя аргумента не указано, как компилятор сможет различить вызовы двух перегруженных программ? И все же ситуаций, в которых связывание по имени отличается по семантическому смыслу от позиционной записи, следует по возможности избегать.
- Списки параметров перегруженных программ не должны различаться только режимом использования параметров. Даже если в одной версии для параметра задан режим
IN
, а в другой —IN OUT, PL/SQL
не видит различия между ними. - Все перегруженные программы должны объявляться в одном и том же блоке PL/SQL или иметь одну область видимости (анонимный блок, пакет, отдельная процедура или функция). Нельзя определить одну версию программы в одном блоке (с его областью видимости), а другую — в другом. Вы не можете перегружать две отдельные программы, так как в этом случае одна из них просто заменит другую.
- Перегруженные функции не могут различаться только типом возвращаемого значения (указанного в предложении
RETURN
функции). В момент вызова перегруженной функции компилятор не знает, значение какого типа она будет возвращать. Поэтому он не может определить, какую из версий функции использовать, если все остальные параметры идентичны.
Перегрузка числовых типов
В OraclelOg
появилась возможность перегрузки двух подпрограмм, различающихся только числовым типом формальных параметров. Рассмотрим конкретный пример:
DECLARE
PROCEDURE procl (n IN PLS_INTEGER) IS
BEGIN
DBMS_OUTPUT.PUT_LINE ('pls_integer version');
END;
PROCEDURE proc1 (n IN NUMBER) IS
BEGIN
DBMS_OUTPUT.PUT_LINE ('number version');
END;
BEGIN
proc1 (1.1);
proc1 (1);
END;
При попытке выполнить этот код в Oracle9i
происходит ошибка:
ORA-06550: line 14, column 4:
PLS-00307: too many declarations of 'PROC1' match this call
Однако при выполнении того же блока в Oracle10g
и Oracle11g
результат будет таким:
number version
pls_integer version
Теперь компилятор PL/SQL
различает два вызова. Обратите внимание: при передаче не-целочисленного значения вызывается «NUMBER
-версия» программы. Это объясняется тем, что PL/SQL
при определении соответствия перебирает типы в следующем порядке: сначала PLS_INTEGER
или BINARY_INTEGER
, затем NUMBER
, BINARY_FLOAT
и наконец, BINARY_DOUBLE
. Используется первая перегруженная программа, которая соответствует переданным значениям фактических аргументов.
Хотя эта новая гибкая возможность чрезвычайно удобна, будьте осторожны при использовании этой крайне неочевидной перегрузки — убедитесь в том, что она работает именно так, как предполагалось. Протестируйте свой код с разными входными данными и проверьте результаты. Помните, что в качестве числового параметра может быть передана строка вида «156.4»; протестируйте и такие данные.
Также можно уточнить тип числовых значений и использовать функции преобразования для явного выбора перегруженной версии. Скажем, если значение 5.0 должно передаваться в формате BINARY_FLOAT
, используйте обозначение 5.0f или функцию преобразования TO_BINARY_FLOAT
(5.0).