Oracle PL/SQL高级编程(第一弹:面向对象编程)

PL/SQL中对象的组成结构

PL/SQL中的对象类型是一种自定义的复合类型,与包的定义很相似,由两部分组成:
- 对象类型规范:是对象与应用的接口,用于定义对象的公用属性和方法。
- 对象类型体:用于实现对象类型规范所定义的公用方法。

在对象类型规范中定义对象属性时,必须要提供属性名和数据类型,对象类型属性可以使用多数Oracle数据类型,但是不能使用以下类型:
- LONG和LONG RAW
- ROWID和UROWID
- PL/SQL特定的类型,如BINARY_INTEGER、BOOLEAN、%TYPE、%ROWTYPE、REF CURSUR、RECORD等。

在定义对象类型的属性时,不能指定对象属性的默认值,也不能指定NOT NULL选项。

对象类型的方法有以下几种:
- 构造方法:该方法类似于Java语言中的构造函数,用来初始化一个对象类型并返回对象的实例。
- MEMBER方法:该方法允许对象的实例进行调用,在MENBER方法中可以访问对象实例的数据,通常称为实例方法或成员方法。
- STATIC方法:该方法可以直接在对象类型上进行调用,它用于在对象类型上执行全局操作,通常称为静态方法。
- MAP方法:用于在多个对象间排序的映射方法。
- ORDER方法:用于在两个对象实例间排序的排序方法。

定义对象类型

定义对象类型的语法比较复杂,选项比较多,所以我们直接来看代码来了解怎么定义一个对象。
首先来定义一个对象类型规范:

--定义对象类型规范employee_obj
CREATE OR REPLACE TYPE employee_obj AS OBJECT (
--定义对象类型属性
empno         NUMBER(4),
ename         VARCHAR2(20),
job           VARCHAR2(20),
sal           NUMBER(10,2),
comm          NUMBER(10,2),
deptno        NUMBER(4),
--定义对象类型方法
MEMBER PROCEDURE Change_sal(p_empno NUMBER,p_sal NUMBER),
MEMBER PROCEDURE Change_comm(p_empno NUMBER,p_comm NUMBER),
MEMBER PROCEDURE Change_deptno(p_empno NUMBER,p_deptno NUMBER),
MEMBER FUNCTION get_sal(p_empno NUMBER) RETURN NUMBER,
MEMBER FUNCTION get_comm(p_empno NUMBER) RETURN NUMBER,
MEMBER FUNCTION get_deptno(p_empno NUMBER) RETURN INTEGER
) NOT FINAL;      --指定该类可以被继承,如果指定FINAL,表示该类无法被继承

然后来定义对象体:

--定义对象类型体
CREATE OR REPLACE TYPE BODY employee_obj
AS
   MEMBER PROCEDURE change_sal (p_empno NUMBER, p_sal NUMBER)
   IS                          --定义对象成员方法,更改员工薪资
   BEGIN
      UPDATE emp
         SET sal = p_sal
       WHERE empno = p_empno;
   END;
   MEMBER PROCEDURE change_comm (p_empno NUMBER, p_comm NUMBER)
   IS                         --定义对象成员方法,更改员工提成
   BEGIN
      UPDATE emp
         SET comm = p_comm
       WHERE empno = p_empno;
   END;
   MEMBER PROCEDURE change_deptno (p_empno NUMBER, p_deptno NUMBER)
   IS                        --定义对象成员方法,更改员工部门
   BEGIN
      UPDATE emp
         SET deptno = p_deptno
       WHERE empno = p_empno;
   END;
   MEMBER FUNCTION get_sal (p_empno NUMBER)
      RETURN NUMBER
   IS                        --定义对象成员方法,获取员工薪资
      v_sal   NUMBER (10, 2);
   BEGIN
      SELECT sal
        INTO v_sal
        FROM emp
       WHERE empno = p_empno;
      RETURN v_sal;
   END;
   MEMBER FUNCTION get_comm (p_empno NUMBER)
      RETURN NUMBER
   IS                        --定义对象成员方法,获取员工提成
      v_comm   NUMBER (10, 2);
   BEGIN
      SELECT comm
        INTO v_comm
        FROM emp
       WHERE empno = p_empno;
      RETURN v_comm;
   END;
   MEMBER FUNCTION get_deptno (p_empno NUMBER)
      RETURN INTEGER
   IS                        --定义对象成员方法,获取员工部门
      v_deptno   INT;
   BEGIN
      SELECT deptno
        INTO v_deptno
        FROM emp
       WHERE empno = p_empno;
      RETURN v_deptno;
   END;
END;

使用SELF关键字

每一个MEMBER类型方法都隐式地声明了一个内联参数SELF,它代表了对象类型的一个实例,总是被传递给成员方法的第一个参数。实际上在方法体内,也可以不用SELF。以下两行代码效果是一样的:

MEMBER FUNCTION get_sal RETURN NUMBER IS
BEGIN
    RETURN SELF.sal;
    --RETURN sal;
END;

当没有显式使用SELF关键字时,实际上也是隐式地使用了这个关键字。

定义构造函数

当定义了 一个对象类型后,系统会提供一个接收与每个属性相对应的参数的构造函数。因此大多数情况下不需要自己再编写构造函数。不过我们也可以处于如下的目的来自定义构造函数:
- 为对象提供初始化功能,以避免许多具有特别用途的过程只初始化对象的不同部分,可以通过构造函数进行统一初始化。
- 可以在构造函数中为某些属性提供默认值。
- 出于维护性的考虑,在新的属性添加到对象中时,避免要更改调用构造函数的应用程序中的代码,这样可以使已经存在的构造函数调用继续工作。

自定义构造函数使用CONSTRUCTOR关键字进行声明。

--定义对象类型规范
CREATE OR REPLACE TYPE salary_obj AS OBJECT (
percent        NUMBER(10,4),       --定义对象属性
sal          NUMBER(10,2),
--自定义构造函数
CONSTRUCTOR FUNCTION salary_obj(p_sal NUMBER) RETURN SELF AS RESULT)
INSTANTIABLE         --可实例化对象
FINAL;               --不可以继承
/
--定义对象类型体
CREATE OR REPLACE TYPE BODY salary_obj
AS
   --实现重载的构造函数
   CONSTRUCTOR FUNCTION salary_obj (p_sal NUMBER)
      RETURN SELF AS RESULT
   AS
   BEGIN
      SELF.sal := p_sal;          --设置属性值
      SELF.PERCENT := 1.12;       --为属性指定初值
      RETURN;
   END;
END;
/

定义MAP和ORDER方法

这两个方法是互斥的,也就是说一次只能在一个对象类型中定义其中一个方法。
- MAP方法:该函数会将对象实例根据一定的调用规则返回DATE、NUMBER、VARCHAR2类型的标量类型,在映射对象类型为标量类型后,就可以通过对标量函数的比较来得到结果了。
- ORDER方法:ORDER方法只能对两个对象进行比较,必须是返回数值型结果的函数,根据结果返回正数、负数或0。该方法只有两个参数,SELF和另一个要比较的对象类型,如果传递该参数为NULL,则返回NULL。

看代码:

--定义一个对象规范,该规范中包含MAP方法
CREATE OR REPLACE TYPE employee_map AS OBJECT (
--定义对象类型属性
   empno    NUMBER (4),
   sal      NUMBER (10, 2),
   comm     NUMBER (10, 2),
   deptno   NUMBER (4),
   MAP MEMBER FUNCTION convert RETURN REAL    --定义一个MAP方法
)
NOT FINAL;            
--定义一个对象类型体,实现MAP函数
CREATE OR REPLACE TYPE BODY employee_map AS
  MAP MEMBER FUNCTION convert RETURN REAL  IS   --定义一个MAP方法
  BEGIN
     RETURN sal+comm;                           --返回标量类型的值
  END;
END;

在定义了MAP函数后,PL/SQL会隐式地通过调用MAP函数在多个对象间进行排序或比较。例如下面创建了一个emp_map_tab的对象表,向这个对象表插入多个对象,然后就可以对这个对象表进行对象的排序:

--创建employee_map类型的对象表
CREATE TABLE emp_map_tab OF employee_map;
--向对象表中插入员工薪资信息对象。
INSERT INTO emp_map_tab VALUES(7123,3000,200,20);
INSERT INTO emp_map_tab VALUES(7124,2000,800,20);
INSERT INTO emp_map_tab VALUES(7125,5000,800,20);
INSERT INTO emp_map_tab VALUES(7129,3000,400,20);

SELECT VALUE(r) val,r.sal+r.comm FROM emp_map_tab r ORDER BY 1;

再来看ORDER方法:

--定义一个对象规范,该规范中包含ORDER方法
CREATE OR REPLACE TYPE employee_order AS OBJECT (
--定义对象类型属性
   empno    NUMBER (4),
   sal      NUMBER (10, 2),
   comm     NUMBER (10, 2),
   deptno   NUMBER (4),
   ORDER MEMBER FUNCTION match(r employee_order) RETURN INTEGER    --定义一个ORDER方法
)
NOT FINAL; 
--定义一个对象类型体,实现ORDER函数
CREATE OR REPLACE TYPE BODY employee_order AS
  ORDER MEMBER FUNCTION match(r employee_order) RETURN INTEGER  IS   
  BEGIN
     IF ((SELF.sal+SELF.comm)<(r.sal+r.comm)) THEN
        RETURN -1;      --可为任何负数
     ELSIF ((SELF.sal+SELF.comm)>(r.sal+r.comm)) THEN
        RETURN 1;       --可为任何正数
     ELSE 
        RETURN 0;      --如果相等则为0
     END IF;
  END match;
END;

定义了ORDER函数后,就可以对两个对象进行比较:

DECLARE
   emp1 employee_order:=employee_order(7112,3000,200,20);    --定义员工1
   emp2 employee_order:=employee_order(7113,3800,100,20);    --定义员工2
BEGIN
   --对员工1和2进行比较,获取返回结果
   IF emp1>emp2 THEN
      DBMS_OUTPUT.put_line('员工1的薪资加提成比员工2大!');
   ELSIF emp1THEN
      DBMS_OUTPUT.put_line('员工1的薪资加提成比员工2小!');
   ELSE
      DBMS_OUTPUT.put_line('员工1的薪资加提成与员工2相等!');
   END IF;
END;

同样,使用ORDER成员方法的对象也可以插入到对象表中,使用对象表的排序功能利用ORDER成员方法进行排序:

--创建employee_order类型的对象表
CREATE TABLE emp_order_tab OF employee_order;
--向对象表中插入员工薪资信息对象。
INSERT INTO emp_order_tab VALUES(7123,3000,200,20);
INSERT INTO emp_order_tab VALUES(7124,2000,800,20);
INSERT INTO emp_order_tab VALUES(7125,5000,800,20);
INSERT INTO emp_order_tab VALUES(7129,3000,400,20);
SELECT VALUE(r) val,r.sal+r.comm FROM emp_order_tab r ORDER BY 1;

使用对象类型

如果声明了一个对象类型的变量而没有进行实例化,那么对象变量的值为NULL,如果尝试读取或设置一个未初始化的对象属性会引发预定义异常ACCESS_INTO_NULL。一个好的编程习惯是在定义对象类型的变量时,就使用构造函数进行初始化,即便不设置值,也可以将构造函数的属性显初始化为NULL,此时对象实例就已经存在,可以使用。代码如下:

DECLARE
   o_emp   employee_order := employee_order (NULL, NULL, NULL, NULL); --初始化对象类型
BEGIN
   o_emp.empno := 7301;                                --为对象类型赋值
   o_emp.sal := 5000;
   o_emp.comm := 300;
   o_emp.deptno := 20;
END;

对象方法的调用如下:

--employee_method对象类型的实例方法与静态方法列表
CREATE OR REPLACE TYPE employee_method AS OBJECT (
   empno    NUMBER (4),
   sal      NUMBER (10, 2),
   comm     NUMBER (10, 2),
   deptno   NUMBER (4),
--定义对象类型方法
   MEMBER PROCEDURE change_sal,            --实例方法,可以访问对象本身的属性
   MEMBER FUNCTION get_sal RETURN NUMBER,   
   --静态方法,不能访问对象本身的属性,只能访问静态数据
   STATIC PROCEDURE change_deptno (empno NUMBER, deptno NUMBER), 
   STATIC FUNCTION get_sal (empno NUMBER) RETURN NUMBER
)
NOT FINAL;    
--演示调用employee_method的实例方法与静态方法
DECLARE
   o_emp employee_method:=employee_method(7369,5000,800,20);
   v_sal NUMBER(10,2); 
BEGIN
   v_sal:=o_emp.get_sal;                       --调用对象实例方法
   DBMS_OUTPUT.put_line('对象实例级别的工资为:'||v_sal);
   v_sal:=employee_method.get_sal(o_emp.empno); --调用静态方法
   DBMS_OUTPUT.put_line('对象类型级别的工资为:'||v_sal);   
END;

使用嵌套对象类型

嵌套对象类型是指在一个对象中嵌入另一个对象类型。来看代码,先定义地址对象类型:

--定义地址对象类型规范
CREATE OR REPLACE TYPE address_type
        AS OBJECT
(street_addr1  VARCHAR2(25),   --街道地址1
 street_addr2  VARCHAR2(25),   --街道地址2
 city          VARCHAR2(30),   --城市
 state         VARCHAR2(20),    --省份
 zip_code      NUMBER,         --邮政编码
 --成员方法,返回地址字符串
 MEMBER FUNCTION toString RETURN VARCHAR2,
 --MAP方法提供地址比较函数
 MAP MEMBER FUNCTION mapping_function RETURN VARCHAR2
 )

--定义地址对象类型体,实现成员方法与MAP函数
CREATE OR REPLACE TYPE BODY address_type
AS
   MEMBER FUNCTION tostring
      RETURN VARCHAR2                    --将地址属性转换为字符形式显示
   IS
   BEGIN
      IF (street_addr2 IS NOT NULL)
      THEN
         RETURN    street_addr1
                || CHR (10)
                || street_addr2
                || CHR (10)
                || city
                || ','
                || state
                || ' '
                || zip_code;
      ELSE
         RETURN street_addr1 || CHR (10) || city || ',' || state || ' '
                || zip_code;
      END IF;
   END;
   MAP MEMBER FUNCTION mapping_function    --定义地址对象MAP函数的实现,返回VARCHAR2类型
      RETURN VARCHAR2
   IS
   BEGIN
      RETURN    TO_CHAR (NVL (zip_code, 0), 'fm00000')
             || LPAD (NVL (city, ''), 30)
             || LPAD (NVL (street_addr1, ''), 25)
             || LPAD (NVL (street_addr2, ''), 25);
   END;
END;
/

再来定义包含地址对象类型的对象类型:

--定义一个对象规范,该规范中包含地址对象类型的属性
CREATE OR REPLACE TYPE employee_addr AS OBJECT (
   empno    NUMBER (4),
   sal      NUMBER (10, 2),
   comm     NUMBER (10, 2),
   deptno   NUMBER (4),
   addr     address_type,
 MEMBER FUNCTION get_emp_info RETURN VARCHAR2   
)
NOT FINAL; 
--定义对象类型体,实现get_emp_info方法
CREATE OR REPLACE TYPE BODY employee_addr
AS
   MEMBER FUNCTION get_emp_info
      RETURN VARCHAR2                    --返回员工的详细信息
   IS
   BEGIN
      RETURN '员工'||SELF.empno||'的地址为:'||SELF.addr.toString;
   END;
END;

使用如下:

DECLARE
   o_address address_type;
   o_emp employee_addr; 
BEGIN
   o_address:=address_type('玉兰一街','二巷','深圳','DG',523343);
   o_emp:=employee_addr(7369,5000,800,20,o_address); 
   DBMS_OUTPUT.put_line('员工信息为'||o_emp.get_emp_info);
END;

对象继承

为了从一个父对象中继承,在子对象中使用UNDER关键字,指定一个父对象名称,该父对象必须是使用NOT FINAL关键字定义的对象。

先来定义一个父对象:

CREATE OR REPLACE TYPE person_obj AS OBJECT (
   person_name        VARCHAR (20),   --人员姓名
   gender      VARCHAR2 (10),          --人员性别
   birthdate   DATE,                  --出生日期
   address     VARCHAR2 (50),         --家庭住址
   MEMBER FUNCTION get_info
      RETURN VARCHAR2                 --返回员工信息
)
NOT FINAL;                            --人员对象可以被继承
CREATE OR REPLACE TYPE BODY person_obj    --对象体
AS
   MEMBER FUNCTION get_info
      RETURN VARCHAR2
   IS
   BEGIN
      RETURN '姓名:' || person_name || ',家庭住址:' || address;
   END;
END;

再来定义子对象:

--对象类型使用UNDER语句从person_obj中继承
CREATE OR REPLACE TYPE  employee_personobj UNDER person_obj (
   empno   NUMBER (6),
   sal     NUMBER (10, 2),
   job     VARCHAR2 (10),
   MEMBER FUNCTION get_emp_info
      RETURN VARCHAR2
);
CREATE OR REPLACE TYPE BODY employee_personobj AS
   MEMBER FUNCTION get_emp_info  RETURN VARCHAR2 IS
   BEGIN
      --在对象类型体中可以直接访问在父对象中定义的属性
      RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job;
   END;
END;

使用如下:

DECLARE
   o_emp employee_personobj;         --定义员工对象类型的变量
BEGIN
   --使用构造函数实例化员工对象
   o_emp:=employee_personobj('张小五','F',
                             TO_DATE('1983-01-01','YYYY-MM-DD'),
                             '中信',7981,5000,'Programmer');
   DBMS_OUTPUT.put_line(o_emp.get_info);          --输出父对象的人员信息
   DBMS_OUTPUT.put_line(o_emp.get_emp_info);      --输出员工对象中的员工信息
END;

可以看到,由于employee_personobj合并了person_obj对象,因此在构造函数中初始化对象时,必须先对父对象中的属性进行初始化,然后初始化子对象类型中的属性。

方法重载

在前面介绍子程序和包时,讨论过重载技术,所为的重载就是定义多个具有同名的函数或过程,但是参数类型或个数不同,由编译器根据调用参数确定执行哪一个子程序。这种重载方式也称为静态多态。在使用对象继承时,也可以使用方法重载。这种重载使用了动态方法调用的能力,也称为动态多态或运行时多态。

对象方法的重载使用OVERRIDING关键字,不是个根据参数的个数或类型来决定调用哪一个方法,而是根据优先级进行调用,也就是总是先调用子类的方法。看代码:

--对象类型使用UNDER语句从person_obj中继承
CREATE OR REPLACE TYPE  employee_personobj UNDER person_obj (
   empno   NUMBER (6),
   sal     NUMBER (10, 2),
   job     VARCHAR2 (10),
   MEMBER FUNCTION get_emp_info
      RETURN VARCHAR2
   --定义重载方法
   OVERRIDING MEMBER MEMBER FUNCTION get_info RETURN VARCHAR2    
);
CREATE OR REPLACE TYPE BODY employee_personobj AS
   MEMBER FUNCTION get_emp_info  RETURN VARCHAR2 IS
   BEGIN
      --在对象类型体中可以直接访问在父对象中定义的属性
      RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job;
   END;
   --实现重载方法
   OVERRIDING MEMBER MEMBER FUNCTION get_info RETURN VARCHAR2 AS
   BEGIN
      RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job; 
   END;          
END;

你可能感兴趣的:(Oracle)