{{announcement.body}}
{{announcement.title}}

Object Type Methods, Part 3

DZone 's Guide to

Object Type Methods, Part 3

In the third part of this series on object types, let's check out member methods, static methods, non-instantiable methods, and more!

· Database Zone ·
Free Resource

Image title

Object Type Methods, Part 3

Packages have subprograms (procedures and functions). Object types have methods.

Object type methods are still procedures and functions, but there are also different types and characteristics of methods that only make sense in an object type, which supports inheritance and dynamic polymorphism.

In this post, the 3rd in my series on object types, I explore:

  • Static methods
  • Member methods
  • Non-instantiable methods
  • Invoking methods of super types
Check out Part 1 and Part 2!

Member Methods

Member methods are methods applied to an instance of the type. Almost all the methods you ever write for an object type will be a member method. Assuming you are already familiar with writing PL/SQL functions and procedures, the most important thing to come up to speed on is the SELF value.

Member methods have a built-in (implicit) parameter named SELF that denotes the object instance currently invoking the method.

We'll explore member methods, including a reminder about overriding, with a to_string function. it's quite common for a class to have a method that returns a string representation. That's what the to_string method will do in our object types.

CREATE OR REPLACE TYPE food_ot AS OBJECT (
   NAME         VARCHAR2 (100),
   food_group   VARCHAR2 (100),
   grown_in     VARCHAR2 (100),
   MEMBER FUNCTION to_string RETURN VARCHAR2
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
   MEMBER FUNCTION to_string RETURN VARCHAR2
   IS
   BEGIN
      RETURN    'FOOD! '
             || SELF.NAME
             || ' - '
             || SELF.food_group
             || ' - '
             || SELF.grown_in;
   END;
END;
/

DECLARE
   squirrels_love_them  food_ot := 
      food_ot ('Acorn', 'Protein', 'Tree');
BEGIN
   DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
END;
/

FOOD! Acorn - Protein - Tree

I could declare SELF explicitly as well, and it works exactly the same:

CREATE OR REPLACE TYPE food_ot AS OBJECT (
   NAME         VARCHAR2 (100),
   food_group   VARCHAR2 (100),
   grown_in     VARCHAR2 (100),
   MEMBER FUNCTION to_string (SELF IN food_ot) RETURN VARCHAR2
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
   MEMBER FUNCTION to_string (SELF IN food_ot) RETURN VARCHAR2
   IS
   BEGIN
      RETURN    'FOOD! '
             || SELF.NAME
             || ' - '
             || SELF.food_group
             || ' - '
             || SELF.grown_in;
   END;
END;
/

DECLARE
   squirrels_love_them  food_ot := 
      food_ot ('Acorn', 'Protein', 'Tree');
BEGIN
   DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
END;
/

FOOD! Acorn - Protein - Tree

What if I want to change the value of an instance's attribute inside my member method? In that case, if it is a function, I must include SELF as a parameter and make it IN OUT. Suppose for example that I want to enforce upper case on all attribute values:

CREATE OR REPLACE TYPE food_ot AS OBJECT (
   NAME         VARCHAR2 (100),
   food_group   VARCHAR2 (100),
   grown_in     VARCHAR2 (100),
   MEMBER FUNCTION to_string (SELF IN OUT food_ot) RETURN VARCHAR2
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
   MEMBER FUNCTION to_string (SELF IN OUT food_ot) RETURN VARCHAR2
   IS
   BEGIN
      /* Enforce upper case for all values */
      SELF.name := UPPER (SELF.name);
      SELF.food_group := UPPER (SELF.food_group);
      SELF.grown_in := UPPER (SELF.grown_in);

      RETURN    'FOOD! '
             || SELF.NAME
             || ' - '
             || SELF.food_group
             || ' - '
             || SELF.grown_in;
   END;
END;
/

DECLARE
   squirrels_love_them  food_ot := 
      food_ot ('Acorn', 'Protein', 'Tree');
BEGIN
   DBMS_OUTPUT.put_line (squirrels_love_them.to_string());
   DBMS_OUTPUT.put_line ('Still upper case? ' || squirrels_love_them.name);
END;
/

FOOD! ACORN - PROTEIN - TREE
Still upper case? ACORN

But as Vasily Suvorov so kindly pointed out in the comments of this post, that is not necessary if your member method is a procedure. Thanks, Vasily!

Static Methods

A static method is a method that is the same (unchanging, static) for all instances of the type. Another way to think about it is that it is a type-level method.

Suppose, for example, that I want to keep track of a version number for my object type. It wouldn't change per instance of the type. It is a characteristic of the type itself. I would define and use that function as follows.

CREATE OR REPLACE TYPE food_ot AS OBJECT
(
   name VARCHAR2 (100),
   food_group VARCHAR2 (50),
   grown_in VARCHAR2 (100),
   STATIC FUNCTION version RETURN VARCHAR2
)
   NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
   STATIC FUNCTION version RETURN VARCHAR2
   IS
   BEGIN
      /* 
      Version history
      2018-09-14 1.0.1 Type deployed to production
      2019-03-22 1.0.2 Added grown_in attribute      
      */
      RETURN '1.0.2';
   END;
END;
/

BEGIN
   DBMS_OUTPUT.put_line ('Version = ' || food_ot.version);
END;
/

Version = 1.0.2

Note that the SELF value isn't available inside static method bodies; static methods have no "current object." If you try, you will get this error:

PLS-00201: identifier 'SELF.attribute_name' must be declared

Static methods are inherited.

CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
  , year_created NUMBER (4) 
);
/

BEGIN
   DBMS_OUTPUT.put_line (dessert_ot.version);
END;
/

1.0.2

They cannot be over-ridden using the OVERRIDING syntax. Instead, when you create a static method with the same name as a supertype, well, that takes precedence.

CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
  , year_created NUMBER (4) 
  , STATIC FUNCTION version RETURN VARCHAR2
);
/

CREATE OR REPLACE TYPE BODY dessert_ot
IS
   STATIC FUNCTION version RETURN VARCHAR2
   IS
   BEGIN
      RETURN 'v10.4.5';
   END;
END;
/

BEGIN
   DBMS_OUTPUT.put_line (dessert_ot.version);
END;
/

10.4.5

What if a supertype has a static function and a subtype has a static procedure with the same name? No problem!

CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
    contains_chocolate CHAR (1)
  , year_created NUMBER (4) 
  , STATIC PROCEDURE version 
);
/

CREATE OR REPLACE TYPE BODY dessert_ot
IS
   STATIC PROCEDURE version 
   IS
   BEGIN
      DBMS_OUTPUT.PUT_LINE ('v10.4.5');
   END;
END;
/

BEGIN
   DBMS_OUTPUT.put_line (dessert_ot.version);
   dessert_ot.version;
END;
/

1.0.2
v10.4.5

That was fun! OK, let's move on to a very interesting nuance when it comes to methods.

Non-Instantiable Methods

This is not a third type of method. It is a way that you can define a member method. Use the NOT INSTANTIABLE clause to define requirements for the API you are building with your type hierarchy and methods.

When you declare a method to be instantiable, you define the header of the method, but not its implementation. This also means that an instance of that type cannot invoke the member method (after all, it's not implemented).

This also means that a subtype must implement an overriding method of the same signature for it to be instantiable (invoked as a method on an instance of the type).

Let's take a look. I will re-create the food type and specify that any subtype must implement a price function for it to be instantiable (the logic here is that "food" all by itself is simply too generic to be able to calculate a price for it - but all subtypes must do so).

CREATE OR REPLACE TYPE food_ot AS OBJECT (
   name VARCHAR2 (100),
   food_group VARCHAR2 (50),
   grown_in VARCHAR2 (100),
   NOT INSTANTIABLE MEMBER FUNCTION price
      RETURN NUMBER
)
NOT FINAL NOT INSTANTIABLE;
/

DECLARE
   l_food food_ot := food_ot ('a', 'b', 'c');
BEGIN
   DBMS_OUTPUT.PUT_LINE (l_food.name);
END;
/

PLS-00713: attempting to instantiate a type that is NOT INSTANTIABLE

I can no longer use the food_ot constructor to initialize a variable based on the food_ot type.

It is still possible, however, to declare a variable using the food_ot and then instantiate and use it, via a subtype constructor, as Vasily Suvorov shows in his LiveSQL script (thanks, Vasily!) and I offer in a simplified format below:

CREATE OR REPLACE TYPE food_ot AS OBJECT ( 
   name VARCHAR2 (100), 
   NOT INSTANTIABLE MEMBER FUNCTION price 
      RETURN NUMBER 
) 
NOT FINAL NOT INSTANTIABLE; 
/

CREATE OR REPLACE TYPE dessert_ot UNDER food_ot ( 
   contains_chocolate   CHAR (1)
 , OVERRIDING MEMBER FUNCTION price 
      RETURN NUMBER 
) 
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY dessert_ot 
IS 
   OVERRIDING MEMBER FUNCTION price 
      RETURN NUMBER 
   IS 
   BEGIN 
      RETURN 1; 
   END; 
END; 
/

DECLARE 
   l_food food_ot ; 
BEGIN 
   l_food := dessert_ot ('Apple', 'N'); 
   DBMS_OUTPUT.PUT_LINE (l_food.name); 
END; 
/

Apple

Notice that when I declare at least one member method to be NOT INSTANTIABLE, I must also do the same for the type as a whole.

Now I will declare the dessert subtype, and implement a price method. I can then instantiate variables of this type.

CREATE OR REPLACE TYPE dessert_ot UNDER food_ot (
   contains_chocolate   CHAR (1)
 , year_created         NUMBER (4)
 , OVERRIDING MEMBER FUNCTION price
      RETURN NUMBER
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY dessert_ot
IS
   OVERRIDING MEMBER FUNCTION price
      RETURN NUMBER
   IS
      multiplier   NUMBER := 1;
   BEGIN
      DBMS_OUTPUT.put_line ('Dessert price!');

      IF SELF.contains_chocolate = 'Y'
      THEN
         multiplier := 2;
      END IF;

      IF SELF.year_created < 1900
      THEN
         multiplier := multiplier + 0.5;
      END IF;

      RETURN (10.00 * multiplier);
   END;
END;
/

DECLARE
   l_apple dessert_ot := dessert_ot ('Apple', 'Fruit', 'Tree', 'N', -5000);
BEGIN
   DBMS_OUTPUT.PUT_LINE (l_apple.name);
END;
/

Apple

A subtype doesn't have to provide an implementation for the price function. It can "pass along" that responsibility to a subtype of its own. But then that type will have to declared NOT INSTANTIABLE.

Invoking Supertype Methods

What if you don't want to completely override a supertype method? What if you want to use it, but also modify its behavior? In that case, it will come in very handy to be able to explicitly invoke a supertype's method.

In the example below, I override the to_string method of the food_ot type to include dessert-specific information in the string returned. But I want to still take advantage of the parent's to_string function. To achieve this effect I use the SELF as parent_type syntax.

CREATE TYPE food_ot AS OBJECT (
   name VARCHAR2 (100),
   food_group VARCHAR2 (50),
   grown_in VARCHAR2 (100),
   MEMBER FUNCTION to_string RETURN VARCHAR2
)
NOT FINAL;
/

CREATE OR REPLACE TYPE BODY food_ot
IS
   MEMBER FUNCTION to_string RETURN VARCHAR2
   IS
   BEGIN
      RETURN    'FOOD! '
             || SELF.NAME
             || ' - '
             || SELF.food_group
             || ' - '
             || SELF.grown_in;
   END;
END;
/CREATE OR REPLACE TYPE dessert_t UNDER food_ot (
    contains_chocolate CHAR (1)
  , year_created NUMBER (4) 
  , OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2
);
/

CREATE OR REPLACE TYPE BODY dessert_ot
IS
   OVERRIDING MEMBER FUNCTION to_string  RETURN VARCHAR2
   IS
   BEGIN
      /* Add the supertype (food) string to the subtype string.... */
      RETURN    'DESSERT! With Chocolate? '
             || contains_chocolate
             || ' created in '
             || SELF.year_created
             || chr(10)
             || '...which is a...'
             || (SELF as food_ot).to_string;
   END;
END;
/

And as you can see, that nested invocation of the supertype method works just fine:

DECLARE
   TYPE foodstuffs_nt IS TABLE OF food_ot;

   fridge_contents foodstuffs_nt
         := foodstuffs_nt (
               food_ot ('Eggs benedict', 'PROTEIN', 'Farm')
             , dessert_ot ('Strawberries and cream'
                        , 'FRUIT', 'Backyard', 'N', 2001)
            );
BEGIN
   FOR indx in 1 .. fridge_contents.COUNT
   LOOP
      DBMS_OUTPUT.put_line (RPAD ('=', 60, '='));
      DBMS_OUTPUT.put_line (fridge_contents (indx).to_string);
   END LOOP;
END;
/

============================================================
FOOD! Eggs benedict - PROTEIN - Farm
============================================================
DESSERT! With Chocolate? N created in 2001
...which is a...FOOD! Strawberries and cream - FRUIT - Backyard

Method Chaining

Method chaining occurs when you connect up a sequence of methods (all of them functions at least until you get to the very end, when you could have a procedure) with dot notation.

It's not something you can do with packaged subprograms, and it can look awfully strange to procedural developers. Here's an example.

Suppose I create this object type:

CREATE OR REPLACE TYPE chaindemo_ot AS OBJECT (
    x NUMBER, y VARCHAR2(10), z DATE,
    MEMBER FUNCTION setx (x IN NUMBER) RETURN chaindemo_ot,
    MEMBER FUNCTION sety (y IN VARCHAR2) RETURN chaindemo_ot,
    MEMBER FUNCTION setz (z IN DATE) RETURN chaindemo_ot);

I can then:

DECLARE
    c chaindemo_ot := chaindemo_ot (NULL, NULL, NULL);
BEGIN
    c := c.setx(1).sety('foo').setz(sysdate); 
END;

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.

Comparing Two Object Type Instances

Can you compare two instances of the same object type for equality? What if you want to implement the concept of an order to an object type, so you can answer the question:

Is Instance A > Instance B?

For that, you need to build a comparison method (MAP or ORDER). I will cover these methods later in this series. So make sure to subscribe to my blog and my Twitter feed so you will see the announcement when I finish it.

But before showing you comparison methods, it will be important to show you how object types can be used inside relational tables. So that's up next!

P.S. Don't forget that if you'd like to try out all this code for yourself, all you have to do is run this LiveSQL script.

The Series: 

Further Reading

Object-Oriented JavaScript

A Guide to High-Performance PL/SQL

Topics:
database ,object type methods ,object types ,member methods ,static methods ,non-instantiable methods ,invoking supertype methods ,method chaining

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}