Methods

I've used two kinds of methods in the previous type definition:

 

Constructor method

A function that accepts values for each attribute and assembles them into a typed object. Declared in lines 5-9 of the example.

 

Member method

A function or procedure that executes in the context of an object instance—that is, it has access to the current values of each of the attributes. Declared in lines 11-12, as well as in lines 14-15 of the example.

My example shows a user-defined constructor, a feature that was introduced in Oracle9i Release 2. Earlier versions provided only a system-defined constructor. Creating your own constructor for each type gives you precise control over what happens at instantiation. That control can be very useful for doing extra tasks like validation and introducing controlled side effects. In addition, you can use several overloaded versions of a user-defined constructor, allowing it to adapt to a variety of calling circumstances.

To see some types and methods in action, take a look at this anonymous block:

1 DECLARE 2 generic_item catalog_item_t; 3 abook book_t; 4 BEGIN 5 abook := NEW book_t(title => 'Out of the Silent Planet', 6 isbn => '0-6848-238-02'); 7 generic_item := abook; 8 DBMS_OUTPUT.PUT_LINE('BOOK: ' || abook.print( )); 9 DBMS_OUTPUT.PUT_LINE('ITEM: ' || generic_item.print( ));10 END;

Interestingly, the objects' print invocations (lines 8 and 9) return the following:

BOOK: id=; title=Out of the Silent Planet; isbn=0-6848-238-02; pages=ITEM: id=; title=Out of the Silent Planet; isbn=0-6848-238-02; pages=

 

Lines 5-6

The constructor assembles a new object and puts it into a book. My example takes advantage of PL/SQL's named notation. It supplied values for only two of the four attributes, but the constructor creates the object anyway, which is what I asked it to do.

The syntax to use any constructor follows the pattern:

[ NEW ] typename ( arg1, arg2, ... );

The NEW keyword, introduced in Oracle9i Release 2, is optional, but is nevertheless useful as a visual cue that the statement will create a new object.

 

Line 7

Something very cool happens here: even though a catalog item is not instantiable, I can assign to it an instance of a subtype, and it will even hold all the attributes that are unique to the subtype. This demonstrates one nifty aspect of "substitutability" that Oracle supports in PL/SQL, which is that by default, an object variable may hold an instance of any of its subtypes.

In English, it certainly makes sense to regard a book as a catalog item. In computerese, it's a case of"widening" or "upcasting" the generic item by adding attributes from a more specific subtype. The converse operation, narrowing, is trickier but nevertheless possible, as you'll see later.

 

Lines 8-9

Notice that the calls to print( ) use the graceful object-style invocation:

object.methodname( arg1, arg2, ...)

because it is a member method executing on an already declared and instantiated object. Which version of the print method executes for objects of different types? The one in the most specific subtype associated with the currently instantiated object. The selection of the method gets deferred until runtime, in a feature known as dynamic method dispatch. This can be very handy, although it may incur a performance cost.

Let's turn now to the body of the book_t method, so we can better understand the result we've just seen. The implementation holds two important new concepts, which I'll describe afterwards.

1 CREATE OR REPLACE TYPE BODY book_t 2 AS 3 CONSTRUCTOR FUNCTION book_t (id IN INTEGER, 4 title IN VARCHAR2, 5 isbn IN VARCHAR2, 6 pages IN INTEGER) 7 RETURN SELF AS RESULT 8 IS 9 BEGIN 10 SELF.id := id; 11 SELF.title := title; 12 SELF.isbn := isbn; 13 SELF.pages := pages; 14 IF isbn IS NULL OR SELF.ck_digit_okay 15 THEN 16 RETURN; 17 ELSE 18 RAISE_APPLICATION_ERROR(-20000, 'ISBN ' || isbn 19 || ' has bad check digit'); 20 END IF; 21 END; 22 23 OVERRIDING MEMBER FUNCTION ck_digit_okay 24 RETURN BOOLEAN 25 IS 26 subtotal PLS_INTEGER := 0; 27 isbn_digits VARCHAR2(10); 28 BEGIN 29 /* remove dashes and spaces */ 30 isbn_digits := REPLACE(REPLACE(SELF.isbn, '-'), ' '); 31 IF LENGTH(isbn_digits) != 10 32 THEN 33 RETURN FALSE; 34 END IF; 35 36 FOR nth_digit IN 1..9 37 LOOP 38 subtotal := subtotal + 39 (11 - nth_digit) * TO_NUMBER(SUBSTR(isbn_digits, nth_digit, 1)); 40 END LOOP; 41 42 /* check digit can be 'X' which has value of 10 */ 43 IF UPPER(SUBSTR(isbn_digits, 10, 1)) = 'X' 44 THEN 45 subtotal := subtotal + 10; 46 ELSE 47 subtotal := subtotal + TO_NUMBER(SUBSTR(isbn_digits, 10, 1)); 48 END IF; 49 50 RETURN MOD(subtotal, 11) = 0; 51 52 EXCEPTION 53 WHEN OTHERS 54 THEN 55 RETURN FALSE; 56 END; 57 58 OVERRIDING MEMBER FUNCTION print 59 RETURN VARCHAR2 60 IS 61 BEGIN 62 RETURN 'id=' || id || '; title=' || title 63 || '; isbn=' || isbn || '; pages=' || pages; 64 END; 65 END;

 

Lines 3-21

A user-defined constructor has several rules to follow:

1. It must be declared with keywords CONSTRUCTOR FUNCTION (line 3)

2. The return clause must be RETURN SELF AS RESULT (line 7)

3. It assigns values to any of the current object's attributes (lines 10-13)

4. It ends with a bare RETURN statement or an exception (line 16; lines 18-19)

A constructor would typically assign values to as many of the attributes as it knows about. As you can see from line 14, my constructor tests the check digit before completing the construction. Notice, by the way, that the ck_digit_okay function reads the current object's isbn attribute (line 30) even before the object has passed validation.

Lines 16-17 are merely a placeholder; you should definitely take a more comprehensive approach to application-specific exceptions, as discussed in Section 6.4.

Next I'd like to discuss the use of the SELF keywords that appears throughout the type body, which, for you Java programmers, is akin to Java's this keyword. Translation for non-Java programmers: SELF is merely a way to refer to the invoking (current) object when writing implementations of member methods. You can use SELF by itself when referring to the entire object, or you can use dot notation to refer to an attribute or a method.

IF SELF.id ... IF SELF.ck_digit_okay( ) ...

The SELF keyword is not always required inside a member method, as you can see in lines 62-63, because the current object's attribute identifiers are always in scope. Using SELF can provide attribute visibility (as in lines 10-13, where the PL/SQL compiler interprets those unqualified identifiers as the formal parameters) and help to make your code SELF-documenting. (Ugh, sorry about that.)

There are a few more rules I'd like to mention about this keyword:

· SELF isn't available insidestatic method bodies because static methods have no "current object." (I'll define static methods later in this section.)

· By default, SELF is an IN variable in functions and an IN OUT variable in procedures and constructor functions.

· You can change the default mode by including SELF as the first formal parameter.

 

Lines 23-56

Computing the check digit is kind of fun, but my algorithm doesn't really exploit any new object-oriented features. I will digress to mention that the exception handler is quite important here; it responds to a multitude of problems such as the TO_NUMBER function encountering a character instead of a digit.

Next, on to creating a subtype for serials:

CREATE OR REPLACE TYPE serial_t UNDER catalog_item_t ( issn VARCHAR2(10), open_or_closed VARCHAR2(1), CONSTRUCTOR FUNCTION serial_t (id IN INTEGER DEFAULT NULL, title IN VARCHAR2 DEFAULT NULL, issn IN VARCHAR2 DEFAULT NULL, open_or_closed IN VARCHAR2 DEFAULT NULL) RETURN SELF AS RESULT, OVERRIDING MEMBER FUNCTION ck_digit_okay RETURN BOOLEAN, OVERRIDING MEMBER FUNCTION print RETURN VARCHAR2) NOT FINAL;

Again, no new features appear in this type, but it does give another example of subtyping. A serial item in this model will have its own constructor, its own version of validating the check digit, and its own way to print itself.[3]

[3] In case you're curious, the open_or_closed attribute will be either (O)pen, meaning that the library can continue to modify the catalog entry (perhaps they do not own all the issues), or (C)losed, meaning that the catalog entry is complete.

In addition to constructor and member methods, Oracle supports two other categories of methods:

 

Static method

A function or procedure invoked independently of any instantiated objects. Static methods behave a lot like conventional PL/SQL procedures or functions. See the sidebar Static Method as Pseudo-Constructor for an example.

 

Comparison method

That is, a map or order method. These are special member methods that let you program what Oracle should do when it needs to compare two objects of this datatype—for example, in anequality test in PL/SQL or when sorting objects in SQL.

One final point before moving on. Objects follow PL/SQL's general convention that uninitialized variables are null;[4] the precise term is atomically null .

[4] Associative arrays are a significant exception; they are non-null but empty when first declared.

As with collections, when an object is null, you cannot simply assign values to its attributes. Take a look at this short example:

DECLARE mybook book_t; -- declared, but not initializedBEGIN IF mybook IS NULL -- this will be TRUE; it is atomically null THEN mybook.title := 'Learning Oracle PL/SQL'; -- this line raises... END IF;EXCEPTION WHEN ACCESS_INTO_NULL -- ...this predefined exception THEN ...END;

Before assigning values to the attributes, you must initialize (instantiate) the entire object in one of three ways: by using a constructor method; via direct assignment from another object; or via a fetch from the database, as the next section discusses.

Static Method as Pseudo-Constructor In Oracle8i through Oracle9i Release 1, the closest thing you could get to a user-defined constructor was a static method that returned an object created by the system-defined constructor. For example: STATIC FUNCTION make (id IN INTEGER, title IN VARCHAR2, isbn IN VARCHAR2, pages IN INTEGER) RETURN book_tISBEGIN IF <various attribute validation tests pass...> THEN RETURN book_t(id, title, isbn, pages); ELSE RAISE <some appropriate exception>; END IF;END; The highlighted code shows where I am using the system-generated constructor, a function Oracle makes available as soon as you create an instantiable type. The constructor always has the same name as the object type, and requires a value for each attribute, in the default order in which attributes are declared in the object type definition. Calling the make function looks like a typical PL/SQL function call: DECLARE a_book book_t := book_t.make(id => 111); Of course, static methods are good for other uses, too.

 

Method Chaining An object whose type definition looks like this: CREATE OR REPLACE TYPE chaindemo_t AS OBJECT ( x NUMBER, y VARCHAR2(10), z DATE, MEMBER FUNCTION setx (x IN NUMBER) RETURN chaindemo_t, MEMBER FUNCTION sety (y IN VARCHAR2) RETURN chaindemo_t, MEMBER FUNCTION setz (z IN DATE) RETURN chaindemo_t); provides the ability to "chain" its methods together. For example: DECLARE c chaindemo_t := chaindemo_t(NULL, NULL, NULL);BEGIN c := c.setx(1).sety('foo').setz(sysdate); -- chained invocation The executable statement above really just acts as the equivalent of: c := c.setx(1);c := c.sety('foo');c := c.setz(sysdate); Each function returns a typed object as the input to the next function in the chain. The implementation of one of the methods appears in the following code (the others are similar): MEMBER FUNCTION setx (x IN NUMBER) RETURN chaindemo_t IS l_self chaindemo_t := SELF; BEGIN l_self.x := x; RETURN l_self; END; Here are some rules about chaining: · You cannot use a function's return value as an IN OUT parameter to the next function in the chain. Functions return read-only values. · Methods are invoked in order from left to right. · The return value of a chained method must be of the object type expected by the method to its right. · A chained call can include at most a single procedure. · If your chained call includes a procedure, it must be the rightmost method in the chain.