一、抽象的角色
抽象是对一个真实世界实体的高级描述或建模。它能排除掉无关的细节内容,使我们的日常生活更有条理。例如,驾驶一辆汽车时,我们是不需要知道它的发动机是如何工作的。由变速排档、方向盘、加速器和刹车组成的接口就能让我们有效地使用它。而其中每一项的详细信息对于日常驾驶来说并不重要。
抽象是编程的核心内容。例如,我们在隐藏一个复杂的算法时只要编写一个过程,然后为它传递参数就可以做到过程化抽象。如果需要改变具体的实现,换一个过程体即可。有了抽象后,那些调用过程的程序就不需要再修改了。
我们在指定变量的数据类型时,可以使用数据抽象。数据类型代表了对于想操作的数值的值集合和操作符集合。比如说一个POSITIVE类型的变量,只能存放正整数,也只能用于加减乘等运算。使用这个变量时,我们不必知道PL/SQL是如何存储整数或是实现算术运算的。
对象类型是大多数编程语言内置类型的一个概括。PL/SQL提供了大量的标量类型和复合类型,每种类型都与一组预定义操作符相关联。标量类型(如 CHAR)是没有内部组成成分的。但复合类型(如RECORD)是有内部组成成分的,并且其中每一个部分都可以被独立操作。同RECORD类型一样,对象类型也是复合类型。但是,它的操作是用户自定义的,而不是预定义的。
目前,我们还不能用PL/SQL定义对象类型。它们必须用CREATE语句创建并存放在Oracle数据库中,这样才能被许多程序所共享。使用对象类型的程序称为客户端程序,它可以声明并操作对象,但并不要求知道对象类型是如何表现数据或实现操作。这就能够让我们分别编写程序和对象类型,即便是在改变了对象实现时也不会影响到程序。因此,对象类型既支持过程化和又支持数据抽象。
二、什么是对象类型
对象类型是一个用户自定义复合类型,它封装了数据结构和操作这个数据结构的函数和过程。数据结构中的变量称为属性,函数和过程称为方法。通常,我们认为对象(如人、车、银行账户)都是有着属性和行为的。例如一个婴儿有性别、年龄和体重这些属性,行为有吃、喝、睡等。对象类型能够让我们把这些内容抽象出来并在应用程序中使用。
使用CREATE TYPE语句创建对象类型的时候,我们实际上是创建了真实世界中某个对象的抽象模板。模板只指定了我们在程序中能用到的属性和行为。比如一个雇员有很多属性,但通常只有他的一部分信息是我们的应用程序所需要的,见下图:
假设我们现在需要编写一个为雇员分发奖金的程序。因为并不是雇员的所有属性都能用于解决这个问题,所以,我们只需设计一个抽象的雇员,拥有与解决问题相关的属性即可:姓名、ID号、部门、职称、工资和级别。然后,设计一些具体的操作方法,例如更改雇员的级别。
下一步就是定义用于数据表现的变量(属性)和用于执行操作的子程序集(方法)。最后,我们把属性和方法封装到对象类型中去。
对象的属性是公有的(对客户端程序可见)。但是,设计良好的程序是不应该直接操作这些属性的,我们应该为这些操作编写相应的方法。这样,雇员数据就能保存在一个合适的状态。
在运行时,我们可以建立抽象雇员的实例(通常称为对象),然后为它的属性赋值。我们可以按照我们的需求创建任意多个实例。每个对象都有姓名、编号、职别等,如下图所示:
三、为什么使用对象类型
对象类型能把大的系统划分为多个逻辑实体,简化系统复杂度。这就使我们可以创建模块化、可维护、可重用的组件。也能让不同小组的程序员并行开发软件组件。
对象类型靠封装数据的操作来把数据维护代码从SQL脚本中分离出来,并把PL/SQL块封装到方法里。使用对象方法可以避免很多在数据访问时带来的负面影响,同时,对象类型隐藏实现细节,更新细节内容时不必修改客户端程序。
对象类型可以为现实数据建模。现实世界中的复杂实体和关系都可以直接映射到对象类型中。并且,对象类型还可以直接映射到面向对象语言(如Java和C++)的类中。
四、对象类型的结构
与包相同,对象类型也有两部分:说明和体,如下图所示。说明部分是应用程序接口;它声明了数据结构(属性集合)和所需的操作(方法)。方法体部分是对已声明方法的实现。
客户端程序要使用到的所有方法都在说明中声明。我们可以把对象说明想象成一个可选的接口,把对象体想象成一个黒盒。我们可以在不改变说明部分的前提下调试,增强或替换对象体,并且不会对客户端程序造成影响。
在一个对象说明中,所有的属性都必须声明在方法之前。只有子程序的声明才需要实现。所以,如果一个对象类型的说明只声明了属性,那么对象类型的体就没有必要了。我们不能在对象体中声明属性。对象说明中的声明都是公有的。
为了能更好的了解结构,请看下面的例子。这是一个复数的对象类型,有实数部分和虚数部分,并有几个与复数操作相关的方法。
CREATE?TYPE?complex?AS?OBJECT(
??rpart???REAL,???--?attribute
??ipart???REAL,
??MEMBER?FUNCTION?plus(x?complex)
????RETURN?complex,???--?method
??MEMBER?FUNCTION?LESS(x?complex)
????RETURN?complex,
??MEMBER?FUNCTION?times(x?complex)
????RETURN?complex,
??MEMBER?FUNCTION?divby(x?complex)
????RETURN?complex
);
CREATE?TYPE?BODY?complex?AS
??MEMBER?FUNCTION?plus(x?complex)
????RETURN?complex?IS
??BEGIN
????RETURN?complex(rpart?+?x.rpart,?ipart?+?x.ipart);
??END?plus;
??MEMBER?FUNCTION?LESS(x?complex)
????RETURN?complex?IS
??BEGIN
????RETURN?complex(rpart?-?x.rpart,?ipart?-?x.ipart);
??END?LESS;
??MEMBER?FUNCTION?times(x?complex)
????RETURN?complex?IS
??BEGIN
????RETURN?complex(rpart?*?x.rpart?-?ipart?*?x.ipart,
???????????????????rpart?*?x.ipart?+?ipart?*?x.rpart);
??END?times;
??MEMBER?FUNCTION?divby(x?complex)
????RETURN?complex?IS
????z???REAL?:=?x.rpart?**?2?+?x.ipart?**?2;
??BEGIN
????RETURN?complex((rpart?*?x.rpart?+?ipart?*?x.ipart)?/?z,
???????????????????(ipart?*?x.rpart?-?rpart?*?x.ipart)?/?z);
??END?divby;
END;
五、对象类型组件
对象类型封装了数据和操作。我们可以在对象类型说明中声明属性和方法,但不能声明常量、异常、游标或类型。我们至少要声明一个属性(最多1000个),方法是可选的。
1、属性
同变量一样,属性也有名称和数据类型。对象类型中的名称必须是唯一的(但在其他的对象类型中可以重用)。除了下面几种类型之外,其他任何Oralce类型都可以使用:
- LONG和LONG RAW
- ROWID和UROWID
- PL/SQL特定类型BINARY_INTEGER及它的子类型、BOOLEAN、PLS_INTEGER、RECORD、REF CURSOR、%TYPE和%ROWTYPE
- PL/SQL包内定义的数据类型
我们不能在声明属性的时候用赋值语句或DEFAULT子句为它初始化。同样,也不能对属性应用NOT NULL约束。但是,对象是可以存放到添加了约束的数据表中。
数据结构中的属性集合依赖于真实世界中的对象。例如,为了表现一个分数,我们只需要两个INTEGER类型的变量。另一方面,要是表现一个学生,我们需要几个VARCHAR2来存放姓名、住址、电话号码和状态等,再添加一个VARRAY类型变量用来存储课程和分数。
数据结构可能是复杂的。例如,一个属性的数据类型可能是另外一个对象类型(称为嵌套对象类型)。有些对象类型,像队列、链表和树,都是动态的,它们是随着使用的需要而动态改变存储长度的。递归对象类型能够直接或间接的包含自身类型,这样就能创建出更诡异的数据类型。
2、方法
一般的,方法就是用关键字MEMBER或STATIC声明在对象说明部分的子程序。方法名不能和对象类型名、属性名重复。MEMBER方法只能通过对象实例调用,如:
instance_expression.method()
但是,STATIC方法直接通过对象类型调用,而不是实例,如:
object_type_name.method()
方法的定义规则与打包子程序的相同,也分为说明和体两个部分。说明部分由一个方法名和一个可选的参数列表组成,如果是函数,还需要包含一个返回类型。包体就是一段能执行一个特殊任务的代码。
对于对象类型说明中的每个方法说明,在对象类型体中都必须有与之对应的方法体实现,除非这个方法是用关键字NOT INSTANTIABLE加以限定,它的意思就是方法体的实现只在子类中出现。为了使方法说明和方法体相匹配,PL/SQL编译器采用token-by- token的方式把它们的头部进行比较。头部必须精确匹配。
与属性相同,一个形式参数的声明也是由名称和数据类型组成。但是,参数的类型不能受到大小约束。数据的类型可以是任何Oracle类型,除了那些不适用于属性的类型。这些约束也适用于返回值的类型。
- 方法实现所允许使用的语言
Oracle允许我们在PL/SQL、Java或C语言中实现对象方法。我们可以在Java或C语言中实现类型方法,只需提供一个调用说明即可。调用说明在Oracle的数据词典中公布了Java方法或外部C函数。它把程序的名称、参数类型和返回值信息映射到对应的SQL中去。
- SELF参数
MEMBER方法接受一个内置的SELF参数,它代表了对象类型的实例。不论显式或隐式声明,它总是第一个传入MEMBER方法的参数。但是,STATIC方法就不能接受或引用SELF。
在方法体中,SELF指定了被调用方法所属的对象实例。例如,方法transform把SELF声明为IN OUT参数:
CREATE?TYPE?Complex?AS?OBJECT?(
??MEMBER?FUNCTION?transform?(SELF?IN?OUT?Complex)?...
我们不能把SELF指定成其他数据类型。在MEMBER函数中,如果SELF没有声明的话,它的参数默认为IN。但是,在MEMBER过程中,如果SELF没有什么,那么它的参数模式默认为IN OUT。并且,我们不能把SELF的模式指定为OUT。
如下例所示,方法可以直接引用SELF的属性,并不需要限定修饰词:
CREATE?FUNCTION?gcd(x?INTEGER,?y?INTEGER)
??RETURN?INTEGER?AS
??--?find?greatest?common?divisor?of?x?and?y
??ans???INTEGER;
BEGIN
??IF?(y?<=?x)?AND(x?MOD?y?=?0)?THEN
????ans????:=?y;
??ELSIF?x?<?y?THEN
????ans????:=?gcd(y,?x);
??ELSE
????ans????:=?gcd(y,?x?MOD?y);
??END?IF;
??RETURN?ans;
END;
CREATE?TYPE?rational?AS?OBJECT(
??num???INTEGER,
??den???INTEGER,
??MEMBER?PROCEDURE?normalize,
??...
);
CREATE?TYPE?BODY?rational?AS
??MEMBER?PROCEDURE?normalize?IS
????g???INTEGER;
??BEGIN
????g??????:=?gcd(SELF.num,?SELF.den);
????g??????:=?gcd(num,?den);???--?equivalent?to?previous?statement
????num????:=?num?/?g;
????den????:=?den?/?g;
??END?normalize;
??...
END;
如果我们从SQL语句中调用了一个空实例(即SELF为空)的MEMBER方法,方法不会被调用,并且会返回一个空值。如果从过程语句调用的话,PL/SQL就会抛出预定义异常SELEF_IS_NULL。
- 重载
与打包子程序一样,同种类型的方法(函数或过程)都能被重载。也就是说,我们可以为不同的方法起相同的名字,只要它们的形式参数在数量、顺序或数据类型上有所不同。当我们调用其中一个方法的时候,PL/SQL会把实参列表和形参列表作比较,然后找出合适的方法。
子类型也可以重载它的基类方法。这种情况下,方法必须有完全相同的形式参数。
如果两个方法只是在参数模式上不同的话,我们是不能进行重载操作的。并且,我们不能因两个函数的返回值类型不同而对它们进行重载。
- MAP和ORDER方法
一个标量类型,如CHAR或REAL的值都有一个预定义的顺序,这样它们之间就能进行比较。但是对象类型的实例没有预定义的顺序。要想对它们进行比较或排序就要调用我们自己实现的MAP函数。在下面的例子中,关键字MAP指明了方法convert()通过把Relational对象影射到REAL型上,来对它们进行排序操作:
CREATE?TYPE?rational?AS?OBJECT(
??num???INTEGER,
??den???INTEGER,
??MAP?MEMBER?FUNCTION?CONVERT
????RETURN?REAL,
??...
);
CREATE?TYPE?BODY?rational?AS
??MAP?MEMBER?FUNCTION?CONVERT
????RETURN?REAL?IS
??BEGIN
????RETURN?num?/?den;
??END?CONVERT;
??...
END;
PL/SQL使用顺序来计算布尔表达式的值,如x < y,并且可以在DISTINCT,GROUP BY和ORDER BY子句中作比较。MAP方法convert()可以返回一个对象在所有Relation对象中的相对位置。
一个对象类型只能包含一个MAP方法,它接受一个内置参数SELF并返回一个标量类型:DATE、NUMBER、VARCHAR2,或是一个ANSI SQL类型,如CHARACTER或REAL。
另外,我们还可以用ORDER方法。一个对象类型只能有一个ORDER方法,它必须是一个能返回数字结果的函数。在下面的例子中,关键字ORDER表明了方法match()可以对两个对象进行比较操作:
CREATE?TYPE?customer?AS?OBJECT(
??ID?????NUMBER,
??NAME???VARCHAR2(20),
??addr???VARCHAR2(30),
??ORDER?MEMBER?FUNCTION?match(c?customer)
????RETURN?INTEGER
);
CREATE?TYPE?BODY?customer?AS
??ORDER?MEMBER?FUNCTION?match(c?customer)
????RETURN?INTEGER?IS
??BEGIN
????IF?ID?<?c.ID?THEN
??????RETURN?-1;???--?any?negative?number?will?do
????ELSIF?ID?>?c.ID?THEN
??????RETURN?1;???--?any?positive?number?will?do
????ELSE
??????RETURN?0;
????END?IF;
??END;
END;
每个ORDER方法都只能接受两个参数:内置参数SELF和另外一个同类型的对象。如果c1和c2是Customer对象,一个c1 > c2这样的比较就会自动调用方法match。该方法能够返回负数、零或正数,分别代表SELF比另外一个对象小、等或大。如果传到ORDER方法的参数任意一个为空,方法就会返回空值。
知道方针:一个MAP方法就好比一个哈希函数,能把对象值影射到标量值,然后用操作符,如<、=等来进行比较。一个ORDER方法只是简单地将两个对象进行比较。
我们可以声明一个MAP方法或是一个ORDER方法,但不同时声明这两个方法。如果我们声明了其中一个,我们就可以在SQL或过程语句中进行对象比较。但是,如果我们没有声明方法,我们就只能在SQL语句中进行等或不等的比较。(两个同类型的对象只有它们对应的属性值相同的时候才相等。)
在对大量的对象进行排序或合并的时候,可以使用一个MAP方法。一次调用会把所有的对象影射到标量中,然后对标量值进行排序。ORDER方法的效率相对比较低,因为它必须反复地调用(它一次只能比较两个对象)。
- 构造方法
每个对象类型都有一个构造方法,它是一个名称与对象类型名称相同的函数,用于初始化,并能返回一个对象类型的新的实例。
Oracle会为每个对象类型生成一个构造函数,其中形参与对象类型的属性相匹配。也就是说,参数和属性是一一对应的关系,并且顺序、名称和数据类型都完全相同。
我们也可以定义自己的构造方法,要么覆盖掉系统定义的构造函数,要么定义一个有着不同方法签名的新构造函数。
PL/SQL从来不会隐式地调用构造函数,所以我们必须显式地调用它。
3、更改已存在对象类型的属性和方法
我们可以使用ALTER TYPE语句来添加、修改或删除属性,并可以为已存在的对象类型添加或删除方法:
CREATE?TYPE?person_typ?AS?OBJECT(
??NAME??????CHAR(20),
??ssn???????CHAR(12),
??address???VARCHAR2(100)
);
CREATE?TYPE?person_nt?IS?TABLE?OF?person_typ;
CREATE?TYPE?dept_typ?AS?OBJECT(
??mgr????person_typ,
??emps???person_nt
);
CREATE??TABLE?dept?OF?dept_typ;
--?Add?new?attributes?to?Person_typ?and?propagate?the?change
--?to?Person_nt?and?dept_typ
ALTER??TYPE?person_typ?ADD?ATTRIBUTE?(picture?BLOB,?dob?DATE)
??CASCADE?NOT?INCLUDING?TABLE?DATA;
CREATE?TYPE?mytype?AS?OBJECT(
??attr1???NUMBER,
??attr2???NUMBER
);
ALTER??TYPE?mytype?ADD?ATTRIBUTE?(attr3?NUMBER),
??DROP?ATTRIBUTE?attr2,
??ADD?ATTRIBUTE?attr4?NUMBER?CASCADE;
在过程编译时,它总是使用当前引用的对象类型版本。在对象类型发生改变时,服务器端引用那个对象类型的过程就变得无效了,在下次过程被调用时它会被自动重新编译。而对于客户端引用被更改过的类型的过程,我们就必须手动编译。
如果从基类删除一个方法,那么也必须修改覆盖被删除方法的子类。我们可以用ALTER TYPE的CASADE选择来判断是否有子类被影响到;如果有子类覆盖了方法,那么语句就会被回滚。为了能成功地从基类删除一个方法,我们可以:
- 先从子类删除方法
- 从基类删除方法,然后用不带OVERRIDING关键字的ALTER TYPE把它重新添加进去
六、定义对象类型
对象类型可以表现现实世界中的任何实体。例如,一个对象类型能表现学生,银行账户,计算机显示器,有理数或者是像队列,栈,链表这样的数据结构。这一节给出了几个完整的例子,让我们了解如何设计对象类型并编写我们自己的对象类型。
目前我们还不能在PL/SQL块、子程序或包中定义对象类型。但是,我们可以在SQL*Plus中用下面的语法来定义它:
CREATE?[OR?REPLACE]?TYPE?type_name
??[AUTHID?{CURRENT_USER?|?DEFINER}]
??{?{IS?|?AS}?OBJECT?|?UNDER?supertype_name?}
(
??attribute_name?datatype[,?attribute_name?datatype]...
??[{MAP?|?ORDER}?MEMBER?function_spec,]
??[{FINAL|?NOT?FINAL}?MEMBER?function_spec,]
??[{INSTANTIABLE|?NOT?INSTANTIABLE}?MEMBER?function_spec,]
??[{MEMBER?|?STATIC}?{subprogram_spec?|?call_spec}
??[,?{MEMBER?|?STATIC}?{subprogram_spec?|?call_spec}]...]
)?[{FINAL|?NOT?FINAL}]?[?{INSTANTIABLE|?NOT?INSTANTIABLE}];
[CREATE?[OR?REPLACE]?TYPE?BODY?type_name?{IS?|?AS}
??{?{MAP?|?ORDER}?MEMBER?function_body;
??|?{MEMBER?|?STATIC}?{subprogram_body?|?call_spec};}
??[{MEMBER?|?STATIC}?{subprogram_body?|?call_spec};]...
END;]
1、PL/SQL类型继承一览
PL/SQL支持单继承模式。我们可以定义对象类型的子类型。这些子类型包括父类型(或超类)所有的属性和方法。子类型还可以包括额外的属性和方法,并可以覆盖超类的方法。
我们还可以定义子类是否能继承于某个特定的类型。我们也可以定义不能直接初始化的类型和方法,只有声明它们的子类才可以进行初始化操作。
有些类型属性可以用ALTER TYPE语句动态的改变。当基类发生变化时,无论是用ALTER TYPE语句还是重新定义基类,子类会自动的应用这些改变的内容。我们可以用TREAT操作符只返回某一个指定的子类的对象。
从REF和DEREF函数中产生的值可以代表声明过的表或视图类型,或是一个或多个它的子类型。
- PL/SQL类继承举例
--?Create?a?supertype?from?which?several?subtypes?will?be?derived.
CREATE?TYPE?person_typ?AS?OBJECT(
??ssn???????NUMBER,
??NAME??????VARCHAR2(30),
??address???VARCHAR2(100)
)
NOT?FINAL;
--?Derive?a?subtype?that?has?all?the?attributes?of?the?supertype,
--?plus?some?additional?attributes.
CREATE?TYPE?student_typ?UNDER?person_typ(
??deptid???NUMBER,
??major????VARCHAR2(30)
)
NOT?FINAL;
--?Because?Student_typ?is?declared?NOT?FINAL,?you?can?derive
--?further?subtypes?from?it.
CREATE?TYPE?parttimestudent_typ?UNDER?student_typ(
??numhours???NUMBER
)
;
--?Derive?another?subtype.?Because?it?has?the?default?attribute
--?FINAL,?you?cannot?use?Employee_typ?as?a?supertype?and?derive
--?subtypes?from?it.
CREATE?TYPE?employee_typ?UNDER?person_typ(
??empid???NUMBER,
??mgr?????VARCHAR2(30)
)
;
--?Define?an?object?type?that?can?be?a?supertype.?Because?the
--?member?function?is?FINAL,?it?cannot?be?overridden?in?any
--?subtypes.
CREATE?TYPE?T?AS?OBJECT?(...,?MEMBER?PROCEDURE?Print(),?FINAL?MEMBER
FUNCTION?foo(x?NUMBER)...)?NOT?FINAL;
--?We?never?want?to?create?an?object?of?this?supertype.?By?using
--?NOT?INSTANTIABLE,?we?force?all?objects?to?use?one?of?the?subtypes
--?instead,?with?specific?implementations?for?the?member?functions.
CREATE?TYPE?Address_typ?AS?OBJECT(...)?NOT?INSTANTIABLE?NOT?FINAL;
--?These?subtypes?can?provide?their?own?implementations?of
--?member?functions,?such?as?for?validating?phone?numbers?and
--?postal?codes.?Because?there?is?no?"generic"?way?of?doing?these
--?things,?only?objects?of?these?subtypes?can?be?instantiated.
CREATE?TYPE?USAddress_typ?UNDER?Address_typ(...);
CREATE?TYPE?IntlAddress_typ?UNDER?Address_typ(...);
--?Return?REFs?for?those?Person_typ?objects?that?are?instances?of
--?the?Student_typ?subtype,?and?NULL?REFs?otherwise.
SELECT?TREAT(REF(p)?AS?REF?student_typ)
??FROM?person_v?p;
--?Example?of?using?TREAT?for?assignment...
--?Return?REFs?for?those?Person_type?objects?that?are?instances?of
--?Employee_type?or?Student_typ,?or?any?of?their?subtypes.
SELECT?REF(p)
??FROM?person_v?p
?WHERE?VALUE(p)?IS?OF(employee_typ,?student_typ);
--?Similar?to?above,?but?do?not?allow?any?subtypes?of?Student_typ.
SELECT?REF(p)
??FROM?person_v?p
?WHERE?VALUE(p)?IS?OF(ONLY?student_typ);
--?The?results?of?REF?and?DEREF?can?include?objects?of?Person_typ
--?and?its?subtypes?such?as?Employee_typ?and?Student_typ.
SELECT?REF(p)
??FROM?person_v?p;
SELECT?DEREF(REF(p))
??FROM?person_v?p;
2、对象类型实例:栈
栈是一个有序集合。栈有一个栈顶和一个栈底。栈中的每一项都只能在栈顶添加或删除。所以,最后一个被加入栈的项会被最先删除。(可以把栈想象成自助餐厅中的盘子。)压栈和退栈操作能够对栈进行后进先出(LIFO)更新。
栈能应用在很多地方。例如,它们可以用在系统编程中控制中断优先级并对递归进行管理。最简单的栈实现就是使用整数数组,数组的一端代表了栈顶。
PL/SQL提供了VARRAY数据类型,它能让我们声明变长数组。要声明变长数组属性,必须先定义变长数组类型。但是,我们不能再对象说明中定义类型,所以,我们只能单独的定义变长数组类型,并指定它的最大长度,具体实现如下:
CREATE?TYPE?IntArray?AS?VARRAY(25)?OF?INTEGER;
现在我们可以编写对象类型说明了:
CREATE?TYPE?stack?AS?OBJECT(
??max_size???INTEGER,
??top????????INTEGER,
??POSITION???intarray,
??MEMBER?PROCEDURE?initialize,
??MEMBER?FUNCTION?FULL
????RETURN?BOOLEAN,
??MEMBER?FUNCTION?empty
????RETURN?BOOLEAN,
??MEMBER?PROCEDURE?push(n?IN?INTEGER),
??MEMBER?PROCEDURE?pop(n?OUT?INTEGER)
);
最后,我们可以编写对象类型体:
CREATE?TYPE?BODY?stack?AS
??MEMBER?PROCEDURE?initialize?IS
??BEGIN
????top?????????:=?0;
????/*?Call?constructor?for?varray?and?set?element?1?to?NULL.?*/
????POSITION????:=?intarray(NULL);
????max_size????:=?POSITION.LIMIT;???--?get?varray?size?constraint
????POSITION.EXTEND(max_size?-?1,?1);???--?copy?element?1?into?2..25
??END?initialize;
??MEMBER?FUNCTION?FULL
????RETURN?BOOLEAN?IS
??BEGIN
????RETURN(top?=?max_size);???--?return?TRUE?if?stack?is?full
??END?FULL;
??MEMBER?FUNCTION?empty
????RETURN?BOOLEAN?IS
??BEGIN
????RETURN(top?=?0);???--?return?TRUE?if?stack?is?empty
??END?empty;
??MEMBER?PROCEDURE?push(n?IN?INTEGER)?IS
??BEGIN
????IF?NOT?FULL?THEN
??????top??????????????:=?top?+?1;???--?push?integer?onto?stack
??????POSITION(top)????:=?n;
????ELSE???--?stack?is?full
??????raise_application_error(-20101,?'stack?overflow');
????END?IF;
??END?push;
??MEMBER?PROCEDURE?pop(n?OUT?INTEGER)?IS
??BEGIN
????IF?NOT?empty?THEN
??????n??????:=?POSITION(top);
??????top????:=?top?-?1;???--?pop?integer?off?stack
????ELSE
??????--?stack?is?empty
??????raise_application_error(-20102,?'stack?underflow');
????END?IF;
??END?pop;
END;
在成员过程push和pop中,我们使用内置过程raise_application_error来关联用户定义的错误消息。这样,我们就能把错误报告给客户端程序而避免把未控制异常传给主环境。客户端程序捕获PL/SQL异常后,可以在OTHERS异常控制句柄中用SQLCODE和SQLERRM 来确定具体的错误信息。下例中,当异常被抛出时,我们就把对应的错误消息输出:
DECLARE
??...
BEGIN
??...
EXCEPTION
??WHEN?OTHERS?THEN
????dbms_output.put_line(SQLERRM);
END;
另外,程序还可以使用编译指示EXCEPTION_INIT把raise_application_error返回的错误编号影射到命名异常中,如下例所示:
DECLARE
??stack_overflow????EXCEPTION;
??stack_underflow???EXCEPTION;
??PRAGMA?EXCEPTION_INIT(stack_overflow,?-20101);
??PRAGMA?EXCEPTION_INIT(stack_underflow,?-20102);
BEGIN
??...
EXCEPTION
??WHEN?stack_overflow?THEN
??...
END;
3、对象类型实例:售票处
假如有一个连锁电影院,每个影院有三个银幕。每个影院有一个售票处,销售三种不同电影的影票。所有影票的价格都为三美元。定期检查影票的销售情况,然后及时补充影票。
在定义代表销售处的对象类型之前,我们必须考虑到必要的数据和操作。对于一个简单的售票处来说,对象类型需要票价、当前影票存量和已售影票的收据这些属性。它还需要一些方法:购票、盘存、补充存量和收集收据。
对于收据,我们可以使用含有三个元素的数组。元素1、2和3各自记录电影1、2和3。要声明一个变长数组属性,我们就必须先像下面这样定义它的类型:
CREATE?TYPE?RealArray?AS?VARRAY(3)?OF?REAL;
现在,我们可以编写对象类型说明:
CREATE?TYPE?ticket_booth?AS?OBJECT(
??price?????????REAL,
??qty_on_hand???INTEGER,
??receipts??????realarray,
??MEMBER?PROCEDURE?initialize,
??MEMBER?PROCEDURE?purchase(movie?INTEGER,?amount?REAL,?CHANGE?OUT?REAL),
??MEMBER?FUNCTION?inventory
????RETURN?INTEGER,
??MEMBER?PROCEDURE?replenish(quantity?INTEGER),
??MEMBER?PROCEDURE?COLLECT(movie?INTEGER,?amount?OUT?REAL)
);
最后,我们可以编写对象类型体:
CREATE?TYPE?BODY?ticket_booth?AS
??MEMBER?PROCEDURE?initialize?IS
??BEGIN
????price??????????:=?3.00;
????qty_on_hand????:=?5000;???--?provide?initial?stock?of?tickets
????--?call?constructor?for?varray?and?set?elements?1..3?to?zero
????receipts???????:=?realarray(0,?0,?0);
??END?initialize;
??MEMBER?PROCEDURE?purchase(movie?INTEGER,?amount?REAL,?CHANGE?OUT?REAL)?IS
??BEGIN
????IF?qty_on_hand?=?0?THEN
??????raise_application_error(-20103,?'out?of?stock');
????END?IF;
????IF?amount?>=?price?THEN
??????qty_on_hand????????:=?qty_on_hand?-?1;
??????receipts(movie)????:=?receipts(movie)?+?price;
??????CHANGE?????????????:=?amount?-?price;
????ELSE???--?amount?is?not?enough
??????CHANGE????:=?amount;???--?so?return?full?amount
????END?IF;
??END?purchase;
??MEMBER?FUNCTION?inventory
????RETURN?INTEGER?IS
??BEGIN
????RETURN?qty_on_hand;
??END?inventory;
??MEMBER?PROCEDURE?replenish(quantity?INTEGER)?IS
??BEGIN
????qty_on_hand????:=?qty_on_hand?+?quantity;
??END?replenish;
??MEMBER?PROCEDURE?COLLECT(movie?INTEGER,?amount?OUT?REAL)?IS
??BEGIN
????amount?????????????:=?receipts(movie);???--?get?receipts?for?a?given?movie
????receipts(movie)????:=?0;???--?reset?receipts?to?zero
??END?COLLECT;
END;
4、对象类型实例:银行账户
在定义银行账户对象类型之前,我们必须考虑一下要使用的数据和操作。对于一个简单的银行账户来说,对象类型需要一个账号、余额和状态这三个属性。所需的操作有:打开帐户,验证账号,关闭账户,存款,取款和余额结算。
首先,我们要像下面这样编写对象类型说明:
CREATE?TYPE?bank_account?AS?OBJECT(
??acct_number???INTEGER(5),
??balance???????REAL,
??status????????VARCHAR2(10),
??MEMBER?PROCEDURE?OPEN(amount?IN?REAL),
??MEMBER?PROCEDURE?verify_acct(num?IN?INTEGER),
??MEMBER?PROCEDURE?CLOSE(num?IN?INTEGER,?amount?OUT?REAL),
??MEMBER?PROCEDURE?deposit(num?IN?INTEGER,?amount?IN?REAL),
??MEMBER?PROCEDURE?withdraw(num?IN?INTEGER,?amount?IN?REAL),
??MEMBER?FUNCTION?curr_bal(num?IN?INTEGER)
????RETURN?REAL
);
然后编写对象体:
CREATE?TYPE?BODY?bank_account?AS
??MEMBER?PROCEDURE?OPEN(amount?IN?REAL)?IS
??--?open?account?with?initial?deposit
??BEGIN
????IF?NOT?amount?>?0?THEN
??????raise_application_error(-20104,?'bad?amount');
????END?IF;
????SELECT?acct_sequence.NEXTVAL
??????INTO?acct_number
??????FROM?DUAL;
????status?????:=?'open';
????balance????:=?amount;
??END?OPEN;
??MEMBER?PROCEDURE?verify_acct(num?IN?INTEGER)?IS
??--?check?for?wrong?account?number?or?closed?account
??BEGIN
????IF?(num?<>?acct_number)?THEN
??????raise_application_error(-20105,?'wrong?number');
????ELSIF(status?=?'closed')?THEN
??????raise_application_error(-20106,?'account?closed');
????END?IF;
??END?verify_acct;
??MEMBER?PROCEDURE?CLOSE(num?IN?INTEGER,?amount?OUT?REAL)?IS
??--?close?account?and?return?balance
??BEGIN
????verify_acct(num);
????status????:=?'closed';
????amount????:=?balance;
??END?CLOSE;
??MEMBER?PROCEDURE?deposit(num?IN?INTEGER,?amount?IN?REAL)?IS
??BEGIN
????verify_acct(num);
????IF?NOT?amount?>?0?THEN
??????raise_application_error(-20104,?'bad?amount');
????END?IF;
????balance????:=?balance?+?amount;
??END?deposit;
??MEMBER?PROCEDURE?withdraw(num?IN?INTEGER,?amount?IN?REAL)?IS
??--?if?account?has?enough?funds,?withdraw
??--?given?amount;?else,?raise?an?exception
??BEGIN
????verify_acct(num);
????IF?amount?<=?balance?THEN
??????balance????:=?balance?-?amount;
????ELSE
??????raise_application_error(-20107,?'insufficient?funds');
????END?IF;
??END?withdraw;
??MEMBER?FUNCTION?curr_bal(num?IN?INTEGER)
????RETURN?REAL?IS
??BEGIN
????verify_acct(num);
????RETURN?balance;
??END?curr_bal;
END;