The term abstract data type is about as dry and technical sounding as you can get. Yet the concept of an abstract data type, or ADT, is something we apply -- or should apply -- in every single one of our application efforts, sometimes without even realizing that we are doing it. An abstract data type is a collection of information and operations that act on that information. An ADT can represent computer objects such as lists, records, stacks, arrays, and tables; but an ADT can also represent real-world objects, such as a company, a product, or the set of assembly line operations at a factory.
The power behind the ADT lies in the first word of its name: abstract. When you create an ADT, you work with objects as opposed to variables, columns, and other computer science items. You perform an abstraction from the implementation details to the "thing in itself" and work on a higher level.
PL/SQL offers several different constructs with which to build and manage ADTs, most importantly the package. The most general description of an ADT, in fact, sounds just like the description of a package: a collection of data and operations on that data.
Every application you build is filled with the need and potential for abstract data types. In order to construct an ADT, however, you first need to be able to identify which data is best represented at a new and higher level of abstraction. You then need to be able to build that ADT so that the programmatic interface to the underlying data is consistent and effective.
You should build an ADT in four phases:
The advantages of working with abstract data types should become clear from the following example of a "progress box". The development team of a financials application found that their Oracle Forms 4.0 application would not change the cursor from an arrow to an hourglass with long-running PL/SQL programs. With a wait-time of between three and five minutes, they were concerned that users would get impatient, click irresponsibly with the mouse, and possibly cause damage. Their solution was to construct a progress box aimed at keeping the user informed of the program's progress.
The progress box consisted of a window named progress_window, a canvas view named cv_progress, and the following three items, all defined in a block named b_progress:
A very clever programmer found that by setting the visual attributes (specifically the font) of the thermometer in the item, the letter "n" would appear as a solid box (blue!) on the screen. Two letters would appear as two blocks and so on. Make the item ten characters long and you have a graphical representation of percentage completion in 10% increments.
Here are the four lines of code needed to show 20% completed:
:B_PROGRESS.PERCENT_DONE := '20 % Complete.'; :B_PROGRESS.THERMOMETER := 'nn'; Show_View('CV_PROGRESS'); synchronize;
Actually, before any particular percentage of completion was displayed, it was necessary to initialize the progress box. The following five lines set the process name (in effect, the title for the bar) and initialized the other items:
SET_WINDOW_PROPERTY ('PROGRESS_WINDOW', Visible, Property_On); :C_CONTROL.NBT_PROCESS_NAME := 'Updating Profit and Loss data...'; :C_CONTROL.NBT_PERCENT_DONE := '0 % Complete.'; :C_CONTROL.NBT_THERMOMETER := 'n'; Show_View('CV_PROGRESS'); synchronize;
This progress box was also needed within a loop, which executed over a period of up to ten years. The development team was able to adapt their basic progress box code to dynamically adjust according to the loop index, year_count:
:B_PROGRESS.PERCENT_DONE := TO_CHAR (round((year_count / years_displayed * 100),0)) || ' % Complete.'; :B_PROGRESS.THERMOMETER := :B_PROGRESS.THERMOMETER || 'n'; Show_View('CV_PROGRESS'); synchronize;
In this case, they simply concatenated another "n" onto the current value of the thermometer for each iteration of the loop.
Of course, they needed to hide the progress box when the program ended:
Hide_View('CV_PROGRESS'); SET_WINDOW_PROPERTY('PROGRESS_WINDOW', Visible, Property_Off); synchronize;
Finally, there were also times when they would use the progress box to display a message, but not use the thermometer. The following code shows the lines required to implement this variation on the progress box:
SET_WINDOW_PROPERTY('PROGRESS_WINDOW', Visible, Property_On); :B_PROGRESS.PROCESS_NAME := 'Building reports...'; :B_PROGRESS.PERCENT_DONE := null; :B_PROGRESS.THERMOMETER := null; Show_View('CV_PROGRESS'); synchronize;
My first impression of their approach was that it was very clever. It had a nice look to it and definitely saved the users from wondering what was happening in their application. Then I took a look inside the form and found that the samples of code I presented previously were repeated over and over again in various forms in many different procedures, triggers, and functions.
The developers had not treated the progress box as a thing in itself, as an abstract data type. Instead, the progress was simply a series of executable statements: set the title, change the thermometer setting, make the view visible, etc. Because they did not abstract those individual statements into an object with rules and structure, they were forced to repeat those statements wherever a progress box of any form was needed. This approach not only required repetition of the statements, it also exposed the particular elements of the implementation (the fact that the letter "n" was needed to created a box on the screen, for example) throughout the application.
Well, the developers worked through these issues and got their application moved into production and everything seemed fine. Then a couple of things happened. First, I was asked to review the code with the objective of reducing code volume and improving performance. Second, we converted the application to Oracle Forms 4.5.
Replacing the sequence of executable statements used to produce the progress box with a procedure call was an obvious way to reduce the amount of code in the application. But the conversion to Oracle Forms 4.5 raised another whole issue: the font used to produce a graphical box from the letter "n" wasn't available any longer in the list of fonts for visual attributes! All the individual assignments of the thermometer to the letter "n" would now do little more than make a blue "n" appear on the screen.
These two factors forced us to completely revamp the progress box implementation. Instead of representing the progress box with the separate lines of code, we needed to create an object called a progress box, figure out the ways that progress box was used in the application, and create modules that would implement those different methods. I wanted to be particularly careful to not let individual programmers hardcode a letter or symbol, like "n", in order to achieve the desired effect in the thermometer item.
After some analysis, I determined that the application used the progress box in three ways:
I then converted this high-level specification to a package specification, as shown in the next section.
The msg procedure simply displays the provided message and hides the thermometer.
The bar procedures takes four parameters as follows:
The hide procedure hides the property box and optionally sets the property of the thermometer item.
The package body needed to consolidate all of the formerly hardcoded executable statements in the application is shown in the next section. Notice that programmers do not specify a character, like "n", when calling the progress.bar procedure. Instead, they just indicate the number of boxes to add to the thermometer. Inside the package, a one-character string named thermom_char is set to a value of "n". If the font requires a change, this single variable is modified and none of the code that calls progress.bar is affected.
/* filename on companion disk: progress.fpp */ PACKAGE BODY progress IS thermom_char VARCHAR2(1) := 'n'; PROCEDURE msg (msg_in IN VARCHAR2) IS BEGIN SET_WINDOW_PROPERTY (handles.progress_window_id, Visible, Property_On); SET_ITEM_PROPERTY (handles.thermometer_id, DISPLAYED, PROPERTY_OFF); :b_progress.percent_done := msg_in; :b_progress.process_name := NULL; :b_progress.thermometer := NULL; SHOW_VIEW (handles.cv_progress_id); synchronize; END; PROCEDURE bar (pct_in IN INTEGER, thermom_in IN INTEGER := 1, init_in IN VARCHAR2 := 'NOINIT', msg_in IN VARCHAR2 := NULL) IS /* Create the string to be added to the thermometer. */ bar_stg VARCHAR2(80) := LPAD (thermom_char, thermom_in, thermom_char); BEGIN SET_WINDOW_PROPERTY (handles.progress_window_id, Visible, PROPERTY_ON); SET_ITEM_PROPERTY (handles.thermometer_id, DISPLAYED, PROPERTY_ON); IF msg_in IS NOT NULL THEN :b_progress.process_name := msg_in; END IF; :b_progress.percent_done := TO_CHAR (pct_in) || ' % Complete'; IF UPPER (init_in) = 'INIT' THEN :b_progress.thermometer := bar_stg; ELSE :b_progress.thermometer := :b_progress.thermometer || bar_stg; END IF; SHOW_VIEW (handles.cv_progress_id); synchronize; END; PROCEDURE hide (item_prop_in IN INTEGER := PROPERTY_OFF) IS BEGIN Hide_View (handles.cv_progress_id); SET_ITEM_PROPERTY (handles.thermometer_id, DISPLAYED, item_prop_in); SET_WINDOW_PROPERTY (handles.progress_window_id, Visible, PROPERTY_OFF); synchronize; END; END progress;
Now I can replace the cumbersome, virtually unmaintainable sequences of statements with a single call to the package modules. The following examples show the "before and after" of the progress box code.
By treating the progress box as an object with rules governing its use and appearance, I was able to greatly reduce the volume of code required. The resulting statements are also much more comprehensible and maintainable.
Copyright (c) 2000 O'Reilly & Associates. All rights reserved.