5 Inheritance, Interfaces, and Polymorphism

This chapter really begins to get into object-oriented concepts. Understanding these concepts is critical to becoming a sound Java programmer.

INHERITANCE AND OBJECT-ORIENTED DESIGN

An important benefit of object-oriented (OO) languages like Java is their ability to use the concept of inheritance. This technique allows a class to be created that extends another class in some fashion. The new class might enhance or specialize the capabilities of the original class. This approach is a great alternative to the dreaded suggestion, “I’ll just copy that program and make a few changes.” Before long, the developer will have two versions of the same program to maintain.

When one class inherits from another class, a relationship is defined between the two. The original class is referred to as the base class or the superclass, and the one that extends it is called the derived class or the subclass. Subclasses can themselves be inherited by other classes; and these new classes can also be inherited. A typical object-oriented design presents a hierarchy of related base classes and derived classes. As a matter of fact, Java itself is an object-oriented design: Everything is inherited from the Object base class (see Figure 5.1).

Image

FIGURE 5.1
The subclasses in this diagram inherit from various superclasses. All of these classes ultimately inherit from the Java base class Object (Java.lang.Object).

To get the greatest benefit out of inheritance, a designer should attempt to organize class structures in advance (a “bottom-up” design). To do this, the designer must analyze application requirements and recognize functions that are similar or are likely to be reused. These must be further examined to abstract the common functionality into an organized set of base classes. Then specializations of these classes are designed and constructed to meet the unique application requirements (a “top-down” implementation).

The process of defining the appropriate base classes and the appropriate class hierarchy is one of the most challenging aspects of proper object-oriented design. However, using a well-designed class hierarchy infrastructure is one of the simplest and most efficient processes in object-oriented coding.

An object-oriented design uses inheritance for other benefits as well. It can provide for efficient enhancements to a system. Modifications to a system need be made only to the appropriate class in the class hierarchy; all derived classes will automatically benefit from this enhancement.

INHERITANCE AND OBJECTS

From a conceptual perspective, each subclass, in a sense, contains its superclass (i.e., its parent class).

Think about it this way. Each instance of a subclass automatically creates an instance of its parent class. Therefore, an object whose class type is a derived class logically has its own identity and its parent’s identity. Both the base object and the derived object are instantiated (created) when the derived object is instantiated. Afterward, the derived object can call functions in the base object without first creating the base object.

In the same way, a class (or program) that creates and uses a derived class can directly call methods in the superclass without explicitly creating the superclass. This is because all of the superclass’s public methods become public methods of the subclass. The subclass can add its own members or modify the behavior of existing methods, but by default, the subclass contains all of the functions of the parent class.

Image

Java actually loads both the base object(s) and the derived object at the same time. It then calls the constructor for each class in hierarchal order, so only one object really exists. But when coding a class inheritance hierarchy, it is helpful to think about a derived class as “containing” its super classes, as you will see in a few pages.

INHERITING METHODS

Suppose that the ErrorMsg class is derived from a TextMessage class, and TextMes-sage contains a public method called getTranslation. This method returns a translated version of the message. ErrorMsg defines only one public method, named getErrorMsg. Even so, ErrorMsg appears to have two public methods, getErrorMsg and getTranslation, as shown in Figure 5.2.

Image

FIGURE 5.2
An object from the ErrorMsg class contains any members from its superclass(es). Its public interface is the sum of all of the public methods it and its superclass(es) define.

CALLER CLASS

A class that uses this type of derived class simply needs to create the ErrorMsg object. The TextMessage object will be automatically created. For example:

// Create an instance of ErrorMsg.
// This will automatically create a new instance of ErrorMsg,
// which will contain the members of the base class TextMessage.
     ErrorMsg myErrorMsg = new ErrorMsg ();
// Call the translate method in the base class TextMessage, using the
// ErrorMsg object reference variable.
     String FrenchText = myErrorMsg.getTranslation ();

Notice that the caller program creates only the subclass (ErrorMsg) and does not need to explicitly create the superclass (TextMessage). Yet it still can call the parent class method (getTranslation), using the reference variable (myErrorMsg) of the subclass. The caller class simply needs to create the subclass; all of the public methods of any inherited base class(es) are instantly available. Likewise, all data members of TextMessage (both public and private data members) are created at the same time. Public data members in TextMessage can be directly accessed by the caller class.

Image

One of the first things that happens when an object is created is that the system calls every constructor in the base class(es) for this object. This is how the compiler makes sure that every class “contains” its base class. The developer does not have to consciously do this: The compiler and runtime system do it automatically.

REDEFINING A METHOD

What if the developer of ErrorMsg needs to extend the getTranslation function of TextMessage in some fashion? In this case, the developer could simply define a function with that name in the class definition for ErrorMsg. Now, the getTranslation method in ErrorMsg will be performed by the caller class and not the getTranslation method in TextMessage.

ERRORMSG CLASS

This ErrorMsg class defines its own getTranslation method. It will be used instead of the getTranslation method in TextMessage when ErrorMsg.getTranslation() is called (see Figure 5.3).

// This class extends the TextMessage class.
// Therefore, TextMessage is the superclass, and ErrorMsg is the subclass.
     public class ErrorMsg extends TextMessage {

          public void setErrorMsg (String inputMsg) {
               ...
// Some logic:
               ...
          }
     }
// Define a method named getTranslation. This overrides the method in the
//  base class.
          public String getTranslation () {
               String localString;
               ...
      // Some logic
               ...
               return (localString);
                     }
     }

Image

FIGURE 5.3
In this definition of ErrorMsg, the derived class (ErrorMsg) overrides the getTranslation method of its superclass (TextMessage). Yet the public interface to ErrorMsg remains the same.

CALLER CLASS

Notice that the way the consumer class uses ErrorMsg does not change, even though you are performing a new function:

     ErrorMsg myErrorMsg = new ErrorMsg ();
// Call the translate method in the subclass ErrorMsg.
     String FrenchText = myErrorMsg.getTranslation ();

EXTENDING A METHOD

The getTranslation method in the derived class (ErrorMsg) could call the original getTranslation method in the superclass (i.e., TextMessage) if needed. This is often necessary with derived methods in order to perform the original method, plus any specific logic in the derived method. The keyword super is the reference variable for the parent object (i.e., the object that was automatically created for this subclass).

ERRORMSG CLASS

This ErrorMsg class defines its own getTranslation method. This method will perform some specialized logic for the ErrorMsg class. It will still use the getTranslation method in the base class to get the translated text, and then will convert the translated text to uppercase.

     public class ErrorMsg extends TextMessage {


          public void setErrorMsg (String inputMsg) {
               ...
       // Some logic
               ...
          }
     }
// Define a method named getTranslation. This method overrides the
// method with the same name in the base class.
          public String getTranslation () {
// Call the base object's method.
               String localString = super.getTranslation ();
// Since this is an error message, change it to all uppercase.
// Perform the toUpperCase function (this is a method that every String has).
               localString = localString.toUpperCase ();
               return (localString);          }
     }

The class hierarchy for ErrorMsg does not change, even though you are performing a new function (see Figure 5.4).

Image

FIGURE 5.4
The class hierarchy for ErrorMsg.

CALLER CLASS

Again, the consumer class using ErrorMsg does not change, even though you are performing a new function

     ErrorMsg    myErrorMsg     = new    ErrorMsg ();
// Call the translate method in the subclass ErrorMsg.
      String FrenchText = myErrorMsg.getTranslation ();

WHY INHERITANCE?

Inheritance is not a concept with a good COBOL equivalent, so you’re probably asking, “What’s the big deal here? Why would anyone want to do this?” The answer is best explained in the context of design patterns, a theory that has become an important concept in OO circles. (This theory is described in Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides, published by Addison Wesley Professional in 1994.) The basic premise of design pattern theory is that many development issues can be organized into related groups. If one uses predefined and generally accepted approaches to solving the problems for that group, developers will readily recognize the approach. As a result, the developer will be guided toward the best implementation of a solution for that problem. The quality of the solutions built based on well understood approaches, or patterns, is improved. Further, the time spent on developing the solution is reduced.

A fundamental design pattern is the idea of objects, or reusable blocks of code, that perform a specific task. The theory is that application development problems (or, more precisely, their solutions) are best addressed by breaking down the problem and implementing the most elegant, flexible solution possible. According to object-oriented theory, a solution based on objects will be cheaper and easier to maintain in the long run. An object-oriented analysis attempts to examine a problem using these criteria:

  • What is generic and what is specific about a particular solution? That is, how can I represent my objects so that the characteristics shared by many objects are isolated from those that are unique to my object?

  • How can I abstract my design so that the things likely to change are isolated from those that are constant? Since maintenance and customization is the most expensive part of any programming effort, it is important to concentrate the portions with high activity in one place, separate from those with modest activity.

  • How can I build a solution that can adapt to changes that have not yet been anticipated? What does it mean to build a structure with flexibility in the right places, without compromising reuse? A rigorous design can often be molded to fit new conditions, without necessarily collapsing the original solution.

When you feel up to it, read the Design Patterns book. It will provide unique assistance in understanding complex problems and give you valuable insights into building elegantly designed solutions.

Inheritance is another example of a design pattern. Inheritance allows you to organize elementary functionality into your base classes. This functionality is then extended in derived classes. However, all classes that inherit a super-type will support some form of the basic functionality. A consumer class can treat all objects of this super-type as if they are the same. Important object-oriented design goals are met, that is, the stable and generic portion of your solution is isolated (in the base class) from the more dynamic and specific portion (in the derived class).

At the same time, these boundaries must be fluid. Derived classes may need to enhance, or possibly supplant, functionality in the base classes, without compromising the structural integrity of the system (i.e., without making things so complicated that they become unmanageable). Since derived classes can override base methods, derived classes can always extend existing method behaviors as needed.

INHERITANCE, OBJECTS, AND COBOL

The simplest way to understand inheritance from a COBOL perspective is to imagine that a subroutine’s interface (that is, the items in its LINKAGE SECTION) can be promoted into another subroutine’s interface. Imagine that a subroutine (NEWSUB, for example) could pick one other subroutine (it must be a subroutine that NEWSUB calls) and that that called subroutine’s LINKAGE SECTION would automatically become part of NEWSUB’s LINKAGE SECTION. If NEWSUB did not have a LINKAGE SECTION defined, then one would be automatically created for it.

NEWSUB COBOL

Here is a definition for a shell of a COBOL program named NEWSUB:

IDENTIFICATION DIVISION.
PROGRAM-ID. NEWSUB.
DATA DIVISION.
WORKING-STORAGE SECTION.
PROCEDURE DIVISION.
START-PROGRAM SECTION.
START-PROGRAM-S.

         EXIT PROGRAM.

Not much there!

NEWSUB COBOL: MYSUB

Now, this is the definition for NEWSUB after NEWSUB inherits from MYSUB:

IDENTIFICATION DIVISION.
PROGRAM-ID. NEWSUB.
DATA DIVISION.
WORKING-STORAGE SECTION.

LINKAGE SECTION.

01 PASSED-MYSUB-CONTROL.
    03   MYSUB-ACTION-SWITCH    PIC X.
         88 MYSUB-ACTION-EVALUATE        VALUE "E".
         88 MYSUB-ACTION-SET-AND-EVALUATE VALUE "S".
    03   MSG-TEXT               PIC X(20).
    03   MSG-SIZE               PIC 9(8).
01 PASSED-TEXT-STRING           PIC X(20).

PROCEDURE DIVISION USING PASSED-MYSUB-CONTROL,
                         PASSED-TEXT-STRING.
 START-PROGRAM SECTION.
 START-PROGRAM-S.
*   Check the passed parameters to see if you should just call MYSUB.
     IF MYSUB-ACTION-EVALUATE OF PASSED-MYSUB-CONTROL OR
        MYSUB-ACTION-SET-AND-EVALUATE OF PASSED-MYSUB-CONTROL
          CALL "MYSUB" USING PASSED-MYSUB-CONTROL,
                             PASSED-TEXT-STRING.

     EXIT PROGRAM.

As a result of this change, NEWSUB appears to have some of the same properties of MYSUB. A program (perhaps CALLER) can call either MYSUB or NEWSUB, with the same control structures and get the same results.

So far, little of this makes much sense. Why not just have CALLER call MYSUB directly? One answer might be that you need to enhance MYSUB without affecting other programs that already call MYSUB. In this case, NEWSUB could perform some additional new logic and call MYSUB for the old logic.

Suppose you want NEWSUB to always translate the stored text field to all uppercase. Programs that needed this feature plus the standard features of MYSUB could call NEWSUB. NEWSUB would now look like this:

NEWSUB COBOL
IDENTIFICATION DIVISION.
PROGRAM-ID. NEWSUB.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 TEMP-TEXT-STRING              PIC X(20).

LINKAGE SECTION.

01 PASSED-MYSUB-CONTROL.
    03   MYSUB-ACTION-SWITCH     PIC X.
         88 MYSUB-ACTION-EVALUATE         VALUE "E".
         88 MYSUB-ACTION-SET-AND-EVALUATE VALUE "S".
     03     MSG-TEXT             PIC X(20).
     03     MSG-SIZE             PIC 9(8).
01 PASSED-TEXT-STRING            PIC X(20).

PROCEDURE DIVISION USING PASSED-MYSUB-CONTROL,
                         PASSED-TEXT-STRING.
START-PROGRAM SECTION.
START-PROGRAM-S.
*   Check the passed parameters to see if you should just call MYSUB.
     IF MYSUB-ACTION-EVALUATE OF PASSED-MYSUB-CONTROL OR
       MYSUB-ACTION-SET-AND-EVALUATE OF PASSED-MYSUB-CONTROL
          MOVE PASSED-TEXT-STRING TO TEMP-TEXT-STRING
          CALL "MYSUB" USING PASSED-MYSUB-CONTROL,
                             TEMP-TEXT-STRING
*   Convert the stored text after calling MYSUB.
          INSPECT MSG-TEXT CONVERTING
            "abcdefghijklmnopqrstuvwxyz" TO
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ".

        EXIT PROGRAM.

As you can see, this approach can meet some basic object-oriented goals. Generic behavior is encapsulated in MYSUB, and more specific behavior is established in NEWSUB.

MORE COBOL OBJECT-ORIENTED DESIGN PATTERNS

There is an even more useful example of inheritance-like behavior in COBOL. As you’ve seen, a COBOL subroutine is generally a serviceable mechanism to encapsulate some algorithm. Sometimes, however, the restrictions of a COBOL subroutine hinder its use in complex solutions. This is because the subroutine is written as a black box, a device with well-defined inputs and equally well-defined outputs.

What happens if your requirements are such that the subroutine fulfills 80 or 90 percent of your requirements but not 100 percent? Normally, you’ll have to choose between these solutions:

  • Extend the function of the subroutine to meet your requirements.

  • Code the specific requirements in your CALLER.

Either solution is fine, provided it is technically possible, but each has some potential deficiencies:

  • What if these requirements are too complex or inappropriately unique for inclusion in a subroutine? What if my extensions might break the original design objectives of the subroutine?

  • What if the requirements can only be met by modifying the subroutine’s logic, not simply its input parameters? For example, it may be necessary to insert additional processing logic into MYSUB’s evaluation function. This may not be possible in either NEWSUB or in CALLER.

Faced with these dilemmas, COBOL developers often make the fateful choice to copy the subroutine’s logic into another program and rework it to meet the requirements at hand. This is a classic example of how reuse objectives are overwhelmed by the complexities of a particular requirement. There is, however, a potential third solution to subroutine reuse in a complex environment. This solution requires planning in advance, but it can yield substantial benefits.

Image

Some of the object-oriented benefits of inheritance can be achieved if a subroutine returns to its caller before completing a function so that the caller can extend or complete that function.

If a subroutine allows this type of interaction, the caller can modify the subroutine’s functions to meet its unique requirements. With this approach, it would not always be necessary to change the subroutine’s functions in order to meet new requirements. Think of this type of subroutine not as a black box, but as a box with gloves that extend into the box. These gloves allow you to safely manipulate the internal workings of the box.

Believe it or not, it is possible to implement a form of inheritance with COBOL, even though the language does not directly support this concept. To do so, the interface will include a set of options switches and “traffic-cop” items. The subroutine and the calling program use these to coordinate the program flow. The option switches are set by the calling program to indicate that a particular function is to be extended. The subroutine checks this flag and, if required, returns to the calling program before completing the function. The calling program then checks the traffic-cop item to see if a mid-function return has been requested. If so, the caller can perform some additional code to extend the subroutine’s function and then return to the subroutine. The subroutine checks the traffic cop and resumes processing the incomplete function.

In Figure 5.5, MYSUB-CONTROL is extended to include the options and traffic-cop items. Using these variables, the calling program can request that any of the functions available with ACTION-SWITCH be extended by the caller. MYSUB returns control to the caller before completing the function. The caller then performs any additional logic for that function as required, and then returns to the subroutine.

Image

FIGURE 5.5
Extending a COBOL subroutine.

CALLER COBOL

This program will call MYSUB. MYSUB will return to this program before it is completely finished with its algorithm so that this program can perform some additional logic. This program will then call MYSUB again so that MYSUB can complete its logic.

 IDENTIFICATION DIVISION.
 PROGRAM-ID. CALLER.
 DATA DIVISION.
 WORKING-STORAGE SECTION.

01 MYSUB-CONTROL.
     03  MYSUB-ACTION-SWITCH               PIC X.
         88 MYSUB-ACTION-INITIALIZE       VALUE "I".
         88 MYSUB-ACTION-EVALUATE         VALUE "E".
         88 MYSUB-ACTION-SET-AND-EVALUATE VALUE "S".
         88 MYSUB-ACTION-GET-CALL-COUNTER VALUE "G".
     03  MYSUB-ACTION-EXTENDED-SWITCH      PIC X VALUE " ".
         88 MYSUB-ACTION-EXTENDED         VALUE "E".
     03  MYSUB-TRAFFIC-COP-IN              PIC X VALUE " ".
         88 MYSUB-IN-FUNCTION-EXTENDED    VALUE "E".
     03  MYSUB-TRAFFIC-COP-OUT             PIC X VALUE " ".
         88 MYSUB-OUT-FUNCTION-EXTENDED   VALUE "E".
     03  MSG-TEXT                          PIC X(20).
     03  MSG-SIZE                          PIC 9(8).
     03  MYSUB-RETURNED-CALL-COUNTER       PIC 9(10).
     03  MYSUB-PRIVATE-ITEMS               PIC X(20).

 01 TEXT-STRING                            PIC X(20).
 01 TEXT-CHANGED-COUNTER                   PIC 99999 VALUE 0.

PROCEDURE DIVISION.
START-PROGRAM SECTION.
START-PROGRAM-S.
*   Initialize MYSUB.
     SET MYSUB-ACTION-INITIALIZE TO TRUE.
     PERFORM CALL-MYSUB.
*   Prepare a text argument for MYSUB.
     MOVE "ANYTEXT" TO TEXT-STRING.
     SET MYSUB-ACTION-EVALUATE TO TRUE.
*   Request that MYSUB return before completion.
*   MYSUB will reset this switch upon completion.
     MOVE "E" TO MYSUB-ACTION-EXTENDED-SWITCH.
     PERFORM CALL-MYSUB WITH TEST AFTER UNTIL
     NOT MYSUB-OUT-FUNCTION-EXTENDED.
     DISPLAY "MSG SIZE ", MSG-SIZE,
            " MSG-TEXT ", MSG-TEXT,
            " MSG TEXT changed ", TEXT-CHANGED-COUNTER,
            " times.".
*   Call MYSUB again without changing the TEXT-STRING.
     PERFORM CALL-MYSUB WITH TEST AFTER UNTIL
          NOT MYSUB-OUT-FUNCTION-EXTENDED.
*   Notice that the changed counter did not increment, but the call
*   counter in MYSUB did.
     MOVE "G" TO MYSUB-ACTION-SWITCH.
     MOVE " " TO MYSUB-ACTION-EXTENDED-SWITCH.
     PERFORM CALL-MYSUB.

     DISPLAY "MSG SIZE ", MSG-SIZE,
            " MSG-TEXT ", MSG-TEXT,
            " MYSUB COUNTER ", MYSUB-RETURNED-CALL-COUNTER,
            " MSG TEXT changed ", TEXT-CHANGED-COUNTER,
            " times.".
 EXIT-PROGRAM.
     EXIT PROGRAM.
     STOP RUN.

CALL-MYSUB SECTION.
CALL-MYSUB-S.
     CALL "MYSUB" USING MYSUB-CONTROL, TEXT-STRING.
*   If MYSUB returns before completion, perform some extended
*   functions.
     IF MYSUB-OUT-FUNCTION-EXTENDED
          PERFORM EXTENSIONS-TO-MYSUB.

 EXTENSIONS-TO-MYSUB SECTION.
 EXTENSIONS-TO-MYSUB-S.
*   Count the number of times MYSUB changes MSG-TEXT.
*   If MYSUB is about to change MSG-TEXT, increment a counter.
     IF MSG-TEXT NOT = TEXT-STRING
          ADD 1 TO TEXT-CHANGED-COUNTER.

MYSUB COBOL

MYSUB will return to CALLER before it is completely finished so that CALLER can perform some additional logic. When CALLER calls MYSUB again, MYSUB will complete its logic.

IDENTIFICATION DIVISION.
PROGRAM-ID. MYSUB.
************************************************************************
* This routine accepts a text item as a parameter and                  *
* evaluates the text. If the text is all spaces,                       *
* MSG-SIZE will be set to 0.                                           *
* If requested, the text item will also be stored in the               *
* passed control structure.                                            *
* If the text item is not passed, then MSG-TEXT                        *
* will be evaluated instead.                                           *
* MYSUB will count the number of times it has been called              *
* with a particular MYSUBx-CONTROL and the number of                   *
* times it has been called using all CONTROLs.                         *
*                                                                      *
* MYSUB must be called with the INITIALIZE action when                 *
* any new CONTROL is to be used.                                       *
*                                                                      *
* MYSUB can return before completing the text evaluation               *
* functions so that the caller can extend this function.               *
************************************************************************
DATA DIVISION.

WORKING-STORAGE SECTION.
01 CALL-COUNTER          PIC 9(10) VALUE 0.
01 ARGUMENT-COUNT        PIC 9.
01 LOCAL-TEXT            PIC X(20).

LINKAGE SECTION.
*   Next is a view of MYSUB-CONTROL that is used by the
*   MYSUB subroutine:
 01 MYSUB-CONTROL.
     03   MYSUB-ACTION-SWITCH              PIC X.
          88 MYSUB-ACTION-INITIALIZE      VALUE "I".
          88 MYSUB-ACTION-EVALUATE        VALUE "E".
          88 MYSUB-ACTION-SET-AND-EVALUATE VALUE "S".
          88 MYSUB-ACTION-GET-CALL-COUNTER VALUE "G".
     03   MYSUB-ACTION-EXTENDED-SWITCH      PIC X.
          88 MYSUB-ACTION-EXTENDED         VALUE "E".
     03   MYSUB-TRAFFIC-COP-IN              PIC X.
          88 MYSUB-IN-FUNCTION-EXTENDED    VALUE "E".
     03   MYSUB-TRAFFIC-COP-OUT             PIC X.
          88 MYSUB-OUT-FUNCTION-EXTENDED   VALUE "E".
     03   MSG-TEXT                          PIC X(20).
     03   MSG-SIZE                          PIC 9(8).
     03   MYSUB-RETURNED-CALL-COUNTER       PIC 9(10).
     03   MYSUB-PRIVATE-ITEMS               PIC X(20).
*   In the subroutine's definition of MYSUB-CONTROL,
*   PRIVATE-ITEMS is redefined with items known only to the subroutine.
     03     FILLER REDEFINES MYSUB-PRIVATE-ITEMS.
            05   MYSUB-PRIVATE-COUNTER         PIC 9(8).
            05   MYSUB-OTHER-PRIVATE-ITEMS     PIC X(12).

01 TEXT-STRING          PIC X(20).

PROCEDURE DIVISION USING MYSUB-CONTROL, TEXT-STRING.

MYSUB-INITIAL SECTION.
MYSUB-INITIAL-S.
*   Perform the function to detect the number of arguments.
     PERFORM GET-ARGUMENT-COUNT.
*   Perform the INITIALIZE function if requested.
     IF MYSUB-ACTION-INITIALIZE
          MOVE 0 TO MYSUB-PRIVATE-COUNTER
     ELSE
*   Prepare the text argument and increment the counters, but only if
*    not "continuing" from an incomplete extended function.
     IF NOT MYSUB-IN-FUNCTION-EXTENDED
          IF ARGUMENT-COUNT = 2
               MOVE TEXT-STRING TO LOCAL-TEXT
          ELSE
               MOVE MSG-TEXT TO LOCAL-TEXT
          END-IF
*   Increment the Global counter.
          ADD 1 TO CALL-COUNTER
*   Increment the Instance counter.
          ADD 1 TO MYSUB-PRIVATE-COUNTER.
*   Process the ACTION-SWITCHES.
*   If requested, return the value in the counter variable.
     IF MYSUB-ACTION-GET-CALL-COUNTER
          MOVE CALL-COUNTER TO MYSUB-RETURNED-CALL-COUNTER
     ELSE IF MYSUB-ACTION-EVALUATE
*   This is a request to evaluate the text item.
          PERFORM EVALUATE-TEXT-ITEM.
*   On normal exits, clear the traffic-cop switches.
     MOVE " " TO MYSUB-TRAFFIC-COP-OUT.
 MYSUB-TRAFFIC-COP-IN.

 EXIT-PROGRAM.
     EXIT PROGRAM.

EVALUATE-TEXT-ITEM SECTION.
EVALUATE-TEXT-ITEM-S.
*   Evaluate the text item, but only if you are not resuming a
*   suspended function.
     IF NOT MYSUB-IN-FUNCTION-EXTENDED
          IF LOCAL-TEXT = SPACES
               MOVE 0 TO MSG-SIZE
          ELSE
               MOVE 1 TO MSG-SIZE
          END-IF
*   If an extended function is requested, return to the caller.
          IF MYSUB-ACTION-EXTENDED
               PERFORM SET-TRAFFIC-COPS-AND-EXIT
          ELSE
*   Else, just move the TEXT in.
               MOVE LOCAL-TEXT TO MSG-TEXT
      ELSE
*   Else, continue the extended function, and move the TEXT in.
          MOVE LOCAL-TEXT TO MSG-TEXT.

SET-TRAFFIC-COPS-AND-EXIT SECTION.
SET-TRAFFIC-COPS-AND-EXIT-S.
    MOVE "E" TO MYSUB-TRAFFIC-COP-OUT.
MYSUB-TRAFFIC-COP-IN.

SET-TRAFFIC-COPS-AND-EXIT-NOW.
    EXIT PROGRAM.

GET-ARGUMENT-COUNT SECTION.
GET-ARGUMENT-COUNT-S.
*   Set ARGUMENT-COUNT to the result.
     CALL "C$NARGS" USING ARGUMENT-COUNT.

This technique can be very useful as a mechanism for defining complex subroutines, that is, those whose functions can be extended by calling programs. For example, a set of complex subroutines might serve as a framework for processing multiple items in a list. The items in the list could be rows read in from a database. The list processing framework might provide standard list-management functions, such as searching and positioning. In this case, the basic control logic necessary to process a list is in the subroutine, and the calling program provides whatever specific processing is required.

With the ability to extend the subroutine built right into the subroutine, it is often possible that these types of routines can be used (reused, actually) to meet new requirements. To make this technique useful, it is important that the subroutine be coded so that it returns to the caller at key points, indicating that the function is not complete. The caller can then perform any specialized code required.

However, a word of caution here: The coding technique necessary to support this mechanism in MYSUB is very ugly and is recommended only to the courageous COBOL programmer. Further, if it is appropriate to create a front end to MYSUB (like NEWSUB), the ugliness must be promoted into NEWSUB. In that case, NEWSUB must define and manage an interface (i.e., a LINKAGE SECTION) very similar to MYSUB so as to allow the return from the MYSUB subroutine to percolate back through NEWSUB into CALLER.

In summary, the concept of inheritance, although it is possible to implement in COBOL, can be difficult to implement and requires a very serious commitment to code reuse. In contrast, Java makes these types of designs readily available and directly supported by the language, which also makes it incredibly easy and intuitive.

INHERITANCE AND JAVA

As I’ve just noted, Java makes implementation of these concepts much easier, even fundamental. New classes can naturally extend existing classes. Developers need not worry about the program flow and interface specification issues I have discussed, since the compiler handles all of that automatically. Developers are encouraged to reuse existing class definitions to meet new needs—in some cases by modifying the base classes, in others by creating specialized classes that inherit the capabilities of the base classes. In Java, code should never be copied to reproduce or modify an existing class’s capabilities.

Java’s syntax to define inheritance is the keyword extends. In the previous examples, ErrorMsg extended the class TextMessage. To add an additional level of inheritance, the example now has ErrorMsg extended by the classes PopupErrorMsg and PrintfileErrorMsg (see Figure 5.6).

Image

FIGURE 5.6
The class hierarchy for ErrorMsg, Printfile ErrorMsg, and PopupErrorMsg.

ERRORMSG CLASS

In this set of classes, ErrorMsg extends TextMessage, while PopupErrorMsg and Print-fileErrorMsg extend ErrorMsg.

public class ErrorMsg extends TextMessage {
          ...
          public void setErrorMsg (String inputMsg) {
          ...
          }
     }

POPUPERRORMSG CLASS

public class PopupErrorMsg extends ErrorMsg {
}

PRINTFILEERRORMSG CLASS

public class PrintfileErrorMsg extends ErrorMsg {
}

Image

In other OO languages, a class can simultaneously extend multiple base classes (this is called multiple inheritance). In Java, a class can extend only one base class. Any Java class that does not explicitly extend another class is, by default, an extension of the Java.lang.Object base class. So all classes ultimately inherit the class Object.

As previously discussed, any instance variables that belong to the base class are contained in the derived class (assuming they are public). Also, public methods of the base class become public methods of the derived class. Of course, the derived class can define additional instance variables as well as new methods.

A Java class that extends another actually has a sort of dual identity: its parent class identity and its own identity. Therefore, all members in the derived class (i.e., both new members defined only in this derived class and members derived from the base class) are referenced in the same way by the calling program.

TEXTMESSAGE CLASS

The following code snippets extend (gruesome pun intended) the previous class definitions in order to highlight the Java syntax that supports and uses these concepts.

public class TextMessage {

     public String msgText;
     public int msgSize;
     ...
     public void getTranslation () {
     ...
     }
}

ERRORMSG CLASS

public class ErrorMsg extends TextMessage {
     ...
     public void setErrorMsg (String inputMsg) {
     ...
     }
}

POPUPERRORMSG CLASS

public class PopupErrorMsg extends ErrorMsg {
     public int windowWidth;
     public int windowHeight;
}

PRINTFILEERRORMSG CLASS

public class PrintfileErrorMsg extends ErrorMsg {
     public int linesToSkip;
}

CONSUMER CLASS

A consumer class can use these derived classes in the following way:

// Create new instances of a popup error message and a print file error
//  message.
     PrintfileErrorMsg myPrintMsg = new PrintfileErrorMsg ();
     PopupErrorMsg   myPopupMsg = new PopupErrorMsg ();
     ...
// Capture the length of the popup message myPopupMsg.
// Note that the public member msgSize is defined in the base class
// ErrorMsg.
// Even so, the derived class instance myPopupMsg contains it, and the
// consumer class uses it as if it were part of myPopupMsg.
     int len = myPopupMsg.msgSize;
// Now, compare the size of the message to the size of the popup window.
//  This public data member is defined in the derived class, not the base
//  class.
//  Yet the same ID (myPopupMsg) is used.
     if (len >= myPopupMsg.windowWidth) {
     ...
     }

Classes that are related through inheritance can use the member access control protected. The keyword protected extends the visibility of a member to any derived classes. Using this keyword, a base class can define members that would be appropriate for a derived class to access but would not be appropriate for a consumer class.

TEXTMESSAGE CLASS

     public class TextMessage {
// This member is available to any class.
          public String msgText;
// This member is available to any class in this package and to all
//  derived classes.
          protected int msgSize;

          ...

    }

ERRORMSG CLASS

     public class ErrorMsg extends TextMessage {
// This member is available only to derived classes
// and not to other classes in the package.
          private protected int counter = 0;
// This member is available to classes in this package.
                char     msgNumber;
// This member is available only to this class.
          private char     interfaceInUse;

               ...
          }

A class’s methods can also be extended by a subclass. That is, a derived class can extend the capabilities of its base class’s methods. This is called overriding the method. A derived class might override a base class’s method to provide some specialized version of this method. A consumer class that creates a derived class can, therefore, call derived functions in the same way (i.e., with the same name and the same parameters) that the original (or base) function was called. The Java runtime will figure out which class method to call (in the original class or in the derived class), based on the actual type of the object. Suppose you have a class hierarchy as shown in the following sections.

ERRORMSG CLASS: OVERRIDE

public class ErrorMsg extends TextMessage {

     ...

     public void setErrorMsg (String inputMsg) {
     ...
     }
}

POPUPERRORMSG CLASS

public class PopupErrorMsg extends ErrorMsg {
     public int windowWidth;
     public int windowHeight;
}

PRINTFILEERRORMSG CLASS

public class PrintfileErrorMsg extends ErrorMsg {
     public int linesToSkip;
     ...

     public void setErrorMsg (String inputMsg) {
     ...
     }
}

The classes ErrorMsg and PrintfileErrorMsg both define a method called setErrorMsg, whereas PopupErrorMsg does not define this method. However, a consumer class can call setErrorMsg using any of these three object types.

CONSUMER CLASS

The Consumer class can now call setErrorMsg, which will exist in all three of these classes.

// Create three objects of various types.
     ErrorMsg          myErrorMsg = new ErrorMsg ();
     PopupErrorMsg     myPopupMsg = new PopupErrorMsg ();
     PrintfileErrorMsg myPrintMsg = new PrintfileErrorMsg ();

// Create an object of type PrintfileErrorMsg, even though its nominal
// type is ErrorMsg.
     ErrorMsg     myAnyMsg = new PrintfileErrorMsg ();
     ...
// Now, call the setErrorMsg function for each of the objects.
     myErrorMsg.setErrorMsg ("Any Text");
     myPopupMsg.setErrorMsg ("Any Text");
     myPrintfile.setErrorMsg ("Any Text");
     myAnyMsg.setErrorMsg ("Any Text");

The first two function calls perform the method setErrorMsg as defined in the class ErrorMsg. The next two function calls will perform the method as defined in the class PrintfileErrorMsg, since these objects belong to a class that has overridden (i.e., defined new versions of) this method.

You may have noticed that the object type of myAnyMsg appears to be a little ambiguous. Is it of type ErrorMsg or of type PrintfileErrorMsg? There are two answers to this question. Since any derived object always contains its base class(es), one answer is that myAnyMsg is of both types. However, since myAnyMsg was created using the constructor for the class PrintfileErrorMsg, it is a PrintfileErrorMsg. And since PrintfileErrorMsg contains an override for the method setErrorMsg, the setErrorMsg method for this object type will be called.

PRINTFILEERRORMSG CLASS

Often, a derived class must perform the original function in the base class. In this case, the derived class can use the operator super to refer to the function in the base class. Note that the base object is not explicitly created with a new statement in the derived class. Instead, it is automatically created at the same time as the derived object.

public class PrintfileErrorMsg extends ErrorMsg {
     public int linesToSkip;
     ...

     public void setErrorMsg (String inputMsg) {
// Call the setErrorMsg function in the base class ErrorMsg.
               super.setErrorMsg (inputMsg);
               if (msgSize != 0) {
                    linesToSkip = 1;
                }
          }
     }

SHARING VARIABLES AND METHODS

When a class derives from another class, it inherits all of the class data members and methods from its base classes if the scope of those data members is not private. This means that all of the variables and methods defined in the base classes are available in the derived classes. For example, look carefully at the statement in PrintfileErrorMsg that sets linesToSkip:

if (msgSize != 0) {
     linesToSkip = 1;
}

Where did the variable msgSize come from? PrintfileErrorMsg did not explicitly define this variable. The only one defined is in TextMessage, which ErrorMsg inherits.

But remember, PrintfileErrorMsg inherits from ErrorMsg (which inherits from TextMessage). Therefore, ErrorMsg, PrintfileErrorMsg, and TextMessage all share the variables defined in TextMessage.

Any of the class instances in this hierarchy can treat msgSize as if it is contained in this class instance. At the same time, if any of these class instances modifies msg-Size, then all of the class instances will see this modification. How does this work? When an instance of PrintfileErrorMsg is created, it automatically creates an instance of ErrorMsg, which automatically creates an instance of TextMessage. The variable msgSize is created when TextMessage is created, and all of these instances share this single copy of the variable msgSize.

HIDING VARIABLES AND METHODS

A derived class can create its own copies of base class variables or methods. To understand this better, let’s examine how variable declarations (class data members) are handled.

Variable declaration statements that are at the beginning of a derived class definition and that are named the same as variables in a base class will cause a new variable to be created. These new variables will hide the base class variables from classes that use the derived class (i.e., create an object of this class type) or that derive from it. Identically named variables from the base classes will not be available to consumers of this derived class.

For example, if ErrorMsg declared its own version of msgSize, then PrintfileErrorMsg would only be able to “see” this version of msgSize. The copy of msg-Size declared in TextMessage would not be available to PrintfileErrorMsg. Only the class that hides a variable can see both versions of the variable. For example, Error-Msg can reference both its version of msgSize and the copy of msgSize declared in TextMessage by using the super. reference variable:

if (super.msgSize != 0) {
    msgSize = 1;
}

THE THIS VARIABLE

Sometimes it is a bit unclear which variable, or method, the programmer intends to reference in a class. This ambiguity is especially true of derived classes, which naturally share names.

To clarify which variable to use, the operator this can be used. this means “the members or methods associated with this instance of the class.” The qualifier this is a sort of generic reference variable automatically created for each object. The assignment of msgSize in the previous ErrorMsg example could be expressed as follows:

if (this.msgSize != 0) {
    this.linesToSkip = 1;
}

this.linesToSkip specifies the linesToSkip variable that is part of this instance of the PrintfileErrorMsg class (i.e., the object pointed to by myPrintfile). ErrorMsg could use the this syntax to reference the two different copies of msgSize it has access to, as follows:

if (super.msgSize != 0) {
    this.msgSize = 1;
}

Figure 5.7 depicts these relationships among the classes. The diagram is in the Universal Modeling Language (UML) notation for static class definitions. Each box represents a class. The top section of each box contains the class name. The next section describes the public data member names and their types. The last box describes methods explicitly defined in each class. Finally, the arrows represent the class hierarchy. In this case, PrintfileErrorMsg and PopupErrorMsg inherit from ErrorMsg. ErrorMsg inherits from TextMessage.

Image

FIGURE 5.7
UML static class notation for ErrorMsg, PrintfileErrorMsg, and PopupErrorMsg.

JAVA INTERFACES

Although a Java class cannot extend more than one base class, it can still implement multiple interfaces, or method signatures that have been defined in another class. This is how Java provides some of the benefits of C++’s multiple inheritance, which is the ability of a single class to extend multiple base classes.

Java’s designers felt that the multiple inheritance model was too complex and unnecessary for most applications, so they arrived at this approach. In Java, super refers to only one class (the base class for this class). At the same time, a class can implement methods modeled on other (unrelated) interface definitions.

A Java interface is like a class but lighter. It does not contain any method logic, and it is not automatically instantiated at runtime. It contains only method signatures (i.e., method names and method parameter types) and no implementation code (i.e., no code block for the method). Therefore, the class that implements an interface will inherit no instance variables or method code. Instead, a class that supports an interface promises to implement the methods described in that interface definition.

You can view an interface as a kind of template, or example of the types of methods an implementing class might provide. It is up to the implementing class to actually perform the appropriate logic required to support the interface definition, since there is no base class automatically created by Java for interfaces. The signature descriptions in an interface are a means of organizing and documenting the types of methods that similar classes will support.

Java uses the keyword implements to indicate that one class implements the interfaces (method signatures and constants) of an interface definition.

WRITELINE INTERFACE

The following describes an abstract method (and its signature) that an implementing class might need.

public interface Writeline {
     public void printLine ();
}

PRINTFILEERRORMSG CLASS

The following declares that this class implements the methods from Writeline. You will mimic the methods described in this interface in the class PrintfileErrorMsg.

public class PrintfileErrorMsg extends ErrorMsg
     implements Writeline {

     ...
}

CALLER CLASS

Using this interface technique, the class PrintfileErrorMsg can now receive a request to print the error message using the printLine method. At the same time, PrintfileErrorMsg can also support the methods in the base class ErrorMsg (such as setErrorMsg):

// Create an instance of a PrintfileErrorMsg.
        PrintfileErrorMsg myPrintMsg = new PrintfileErrorMsg ();
// Call the derived method in PrintfileErrorMsg.
        myPrintMsg.setErrorMsg ("Any Text");
// Call the interface method in PrintfileErrorMsg.
        myPrintMsg.printLine();

One key difference between these two method implementations is that the class PrintfileErrorMsg can override the setErrorMsg method, but if it does not, the method in the base class (ErrorMsg) will be executed. However, in order to receive a request to print its error message (the printLine method), PrintfileErrorMsg must implement this method, since there is no real base class method that can be performed.

USING INTERFACES

Interfaces are often supported by helper classes that perform the basic functions of that interface. These supporting classes provide basic implementations of the defined interface. A class that implements a particular interface can simply use these supporting classes as is or implement additional features in addition to the basic features provided.

      public class WriteLineImpl {
//   This class provides the implementation defined in the WriteLine
//   Interface.
           public void printLine (String inputMsg) {
               System.out.println (inputMsg);
         }
}

//   PrintfileErrorMsg will use the helper class WriteLineImpl to
//   implement the method defined in the WriteLine Interface.
      public class PrintfileErrorMsg extends ErrorMsg
           implements Writeline {
//   Create an instance of the helper class.
              WriteLineImpl writeLineImpl = new WriteLineImpl();
           ...
           public void printLine (String inputMsg) {
//   Use the helper class method to implement this method.
              writeLineImpl. printLine (inputMsg);
             return;
          }
      }

This combination of interfaces and supporting classes is often used within a package called a framework. A good example of a framework is the collections framework first provided with Java 1.2. I will review the collections frameworks in some detail in Chapter 11.

HIDING METHODS AND MEMBERS

Derived classes have to be careful with the use of the static keyword when overriding methods in its superclasses. Instance methods (i.e., nonstatic methods) cannot override static methods in the superclass, and static methods cannot override instance methods in the superclass.

If a derived class declares a method as static, then any static methods with the same signature in the superclass(es) will not be readily available to the derived class. The superclass method can be accessed explicitly by using the super keyword or an explicit reference to the superclass type (e.g., TextMessage.staticMethod). If a derived class tries to override an instance method with a static method, the compiler will complain. Finally, a static method in a superclass cannot be overridden with an instance method in the derived class.

On the other hand, a derived class can hide variables in the superclass, using the static keyword. If a derived class declares a variable as static, then any instance variables with the same name in the superclass(es) will not be readily available to the derived class. The superclass variables can be accessed explicitly by using the super keyword but not with an explicit reference to the superclass type (e.g., TextMessage.static-Variable). However, a derived class can override an instance method with a static method, and vice versa, without causing the compiler to complain.

POLYMORPHISM

Polymorphism is one of those buzzwords that sounds terrifying at first but, once understood, elicits the question, “Is that all there is?”

You have already seen that invoking a method in a particular object is sometimes referred to as a message in OO parlance. Often, more than one class can handle a particular type of message. The actual function performed inside any class depends on the algorithm of the class. Therefore, a particular message (that is, a combination of method name and parameters) can be acted on, or interpreted, differently by different classes. Polymorphism is defined as the ability of different classes to support methods and properties of the same name but with different implementations.

A very common example of polymorphism is the ability of most PC applications to process an open (FileName) message. This message will result in very different actions by various applications, but the message, or interface, is the same. An originating application (such as Windows Explorer or an e-mail package) can send this same message to any application, confident that the receiving application will process it correctly.

Classes that are related by inheritance can each accept messages defined in the superclass and are, therefore, polymorphic. Take another look at the previous examples, in which the objects myErrorMsg, myPopupMsg, myPrintfile, and myAnyMsg could all receive the same message (setErrorMsg (Any Text)). Some of the objects were of the same class, so it would be expected that they could handle this message. Others were of different classes, yet they can all receive this message and process it correctly. This is an example of polymorphism.

Unrelated classes can also be polymorphic—that is, two unrelated classes can potentially accept the same message and respond in unique ways to it.

Suppose the system had a class called Diagnostic, and its purpose was to record system events for the purposes of on-site diagnostics. This class may also have a method called setErrorMsg that accepted a text String parameter. An object of this type could receive the same message (setErrorMsg (Any Text)) as the ErrorMsg class did. Java interfaces are a great way to define polymorphic behavior.

Polymorphism delivers a number of advantages to the developer. A program can send a particular message to a whole group of classes without considering the differences between those classes. This has the potential for greatly simplifying code, since distinct objects can be treated in some cases as if they are of the same type. In Java, classes that are polymorphic should be either within the same inheritance hierarchy or implement the same interface. An example might be a component-based system that consisted of many transactional objects of various types (customers, invoices, etc.). When the system needed each of the objects to commit their data to the database, a single message (commit()) could be sent to all of the objects.

That’s all there is to it! Polymorphism is no more than the ability of multiple class types to receive the same message and process it in unique ways.

EXERCISES

It’s time to visit the example classes again and try out all these new ideas.

  1. Using a text editor, edit a new Java source file and name it TextMessage.java in the java4cobol directory. Enter this code in the class body. Note that the two member variables are the same name as the member variables in ErrorMsg. You will remove these variables from ErrorMsg in a moment.

    /
    //
    // TextMessage
    //
    //
    class TextMessage {
    // Define some public class instance variables.
              public String msgText;
              public int msgSize;
              public static char LANGUAGECODE = 'E';

              public void setMsgText (String inputMsg) {
    // Set the msgText and msgSize variables.
                   msgText = inputMsg;
    // Set this variable to the size of the text String.
                   msgSize = msgText.length ();
              }
              public String getTranslation () {
    // Perform the translation function for this message.
    // In a production environment, the translation for this message might
    // be accessed from a database.
    // In this sample, you will return the original text
    // and "French Text" as a generic translation.
                   if (LANGUAGECODE == 'E')
                        return (msgText);
                   else
                        return (msgText + " - → French Text");
              }
    }

  2. Save this class definition as a text file, then compile the class in the DOS command window:

    →   javac TextMessage.java

  3. Edit the ErrorMsg class with the text editor. You will define it as a class that inherits from TextMessage and delete some class variables. Add the bolded code, and remove the italicized code in the beginning of the file, as identified here:

    public class ErrorMsg extends TextMessage {
    // Define some public class instance variables.
              public String msgText;
              public int msgSize;
    // Define some private class instance variables.
              private int     counter = 0;
                      char    interfaceInUse;
    // Define a public method.
              public void setErrorMsg (String inputMsg) {
    // Define a local variable and increment it.
                   int localCounter = 0;
                   localCounter = localCounter + 1;
    // Modify some of the private variables.
                   counter = counter + 1;
                   interfaceInUse = 'S';
    // Modify one of the public variables. Set this variable to the text
    // String that was passed as a parameter.
                   msgText = inputMsg;
    // Set this variable to the length of the text String.
                   msgSize = msgText.length ();

                   setMsgText (inputMsg);
              ...

  4. Save this class definition as a text file, then compile the class in the DOS command window:

    →   javac ErrorMsg.java

  5. Let’s simplify the HelloWorld application and add a call to this new method using the text editor. Open the HelloWorld.java source file and remove the lines after this statement (but remember to leave in the two curly braces at the end of the program):

    // Print the contents of ErrorMsgs String data member directly.
              System.out.println (myErrorMsg.msgText);

    Add these bolded lines:

    // Get the translation for this method.
              tempMsg = myErrorMsg.getTranslation ();
              System.out.println (tempMsg);

  6. Save, compile, and rerun the HelloWorld application.

    →   javac HelloWorld.java

    →   java HelloWorld

    The output should look like this:

    Hello World!

    Some Text
    Some Text
    Some Text

  7. Edit the HelloWorld application, and set LANGUAGECODE to F. Add this bolded line after the statement that creates ErrorMsg:

    // Create a new instance of the ErrorMsg class:
    ErrorMsg myErrorMsg = new ErrorMsg ();
    TextMessage.LANGUAGECODE = 'F';

  8. Save, compile, and rerun the HelloWorld application, and observe the output:

    Hello World!

    Some Text
    Some Text
    Some Text -> French Text

  9. Create a new instance of ErrorMsg, and test how it performs translations. Add these bolded lines to the end of HelloWorld:

    // Create a new instance of the ErrorMsg class.
              ErrorMsg myErrorMsg2 = new ErrorMsg ();
    // Set the text item to some text String, and print its contents.
              myErrorMsg2.setErrorMsg ("Some Text for #2");
              tempMsg = myErrorMsg2.getErrorMsg ();
              System.out.println (tempMsg);
              tempMsg = myErrorMsg2.getTranslation ();
              System.out.println (tempMsg);

  10. Save, compile, and rerun the HelloWorld application, and then observe the output. Notice how LANGUAGECODE applies to all instances of TextMessage:

    Hello World!

    Some Text
    Some Text
    Some Text -> French Text
    Some Text for #2
    Some Text for #2 -> French Text

  11. Let’s create and use the PrintfileErrorMsg class. Create a new java source file in the C:java4cobol directory named PrintfileErrorMsg.java. Insert these lines of text into the class file definition:

    //
    //
    // PrintfileErrorMsg
    //
    //
    class PrintfileErrorMsg extends ErrorMsg {

         private static int outputLineSize = 80;
         public int linesToSkip = 0;
         private int charsToSkip = 0;
    // Define a setErrorMsg method that establishes the number of characters
    // to output in order to center the text from ErrorMsg.
         public void setErrorMsg (String inputMsg) {
              super.setErrorMsg (inputMsg);
              charsToSkip = (outputLineSize - msgSize) / 2;
    // Print out this error message.
              printLine ();
         }
    // Define a new method that prints this error message to the standard
    // output. It will be centered in the output line (based on the size of
    // outputLineSize). linesToSkip lines will be skipped first.
         public void printLine () {
              int i;
    // Print out some blank lines.
              for (i=0; i != linesToSkip; i++)
                   System.out.println ();
    // Print out some blank characters so that the error message text is
    //  centered.
              for (i=0; i != charsToSkip; i++)
                   System.out.print (' '),
    // Print out the error message.
              System.out.println (getErrorMsg ());
         }
    }

  12. Save and then compile this class in the DOS command window:

    →   javac PrintfileErrorMsg.java

  13. Edit the HelloWorld application so that it uses this new class. Using the text editor, open the HelloWorld.java source file and add these bolded lines of code to the bottom of the class:

    // Create a new instance of the PrintfileErrorMsg class.
         PrintfileErrorMsg myErrorMsg3 = new PrintfileErrorMsg ();
         myErrorMsg3.linesToSkip = 2;
    // Set the text item to some text String, and print its contents.
         myErrorMsg3.setErrorMsg ("Some Text for #3");
    // Print this data member in PrintfileErrorMsg.
         System.out.println ("msgSize for PrintfileErrorMsg = " +
            myErrorMsg3.msgSize);

  14. Save, compile, and rerun the HelloWorld application. The output should look like this:

    Hello World!

    Some Text
    Some Text
    SOME TEXT -> FRENCH TEXT
    Some Text for #2
    SOME TEXT FOR #2 -> FRENCH TEXT
          Some Text for #3
    MsgSize for PrintfileErrorMsg = 16

  15. Let’s create a class data member in PrintfileErrorMsg that hides the variable with the same name in MessageText. You will experiment with this variable and see how it affects the classes that use PrintfileErrorMsg. Using the text editor, open the PrintfileErrorMsg.java file, and add these bolded lines (pay particular attention to the super. and this. identifiers for msgSize).

    //
    //
    // PrintfileErrorMsg
    //
    //
    class PrintfileErrorMsg extends ErrorMsg {

         private static int outputLineSize = 80;
         public int linesToSkip = 0;
         private int charsToSkip = 0;
    // Create a version of this variable that hides the one in TextMessage.
         public int msgSize;

    // Define a setErrorMsg method that establishes the number of
    // chars to output in order to center the error msg.
         public void setErrorMsg (String inputMsg) {
              super.setErrorMsg (inputMsg);
              charsToSkip = (outputLineSize - super.msgSize) / 2;
              this.msgSize = super.msgSize + charsToSkip;
              ...

    At the bottom of the class definition insert the bolded lines as follows:

    // Print out the error message.
              System.out.println (getErrorMsg ());
    // Print out the two msgSize variables.
              System.out.println ("this.msgSize = " + this.msgSize + ",
                       super.msgSize = " + super.msgSize);

  16. Save and compile this class:

    →   javac PrintfileErrorMsg.java

  17. Rerun the HelloWorld application. The output should look like this:

    Hello World!

    Some Text
    Some Text
    SOME TEXT -> FRENCH TEXT
    Some Text for #2
    SOME TEXT FOR #2 -> FRENCH TEXT


           Some Text for #3
    this.msgSize = 48, super.msgSize = 16
    MsgSize for PrintfileErrorMsg = 48

  18. Let’s create an example of an interface definition and a supporting class and have PrintfileErrorMsg use them. Create a new java source file in the C:java4cobol directory named WriteLineInterface.java. Insert these lines of text into the class file definition:

    //
    // WriteLineInterface
    //
    //
    public interface WriteLineInterface {
    // Describe an abstract method (and its signature) that an implementing
    // class might need.
         public void printLine ();
    }

  19. Compile this class in the DOS command window:

    →   javac WriteLineInterface.java

  20. Create a new java source file in the C:java4cobol directory named Write-Line.java. Insert these lines of text into the class file definition:

    //
    //
    // WriteLine
    //
    //
    public class WriteLine {
    // Define a STATIC class method that will print out a line.
         public static void printLineWithPosition ( String line,
             int linesToSkip, int charsToSkip) {

             int i;
    // Print out some blank lines.
              for (i=0; i != linesToSkip; i++)
                     System.out.println ();
    // Print out some blank characters.
              for (i=0; i != charsToSkip; i++)
                   System.out.print (' '),
    // Print out the error message.
              System.out.println (line);

         }
    }

  21. Compile this class in the DOS command window:

    →   javac WriteLine.java

  22. Edit the PrintfileErrorMsg class so that it uses this new interface definition and the supporting class. Add these bolded lines of code to the top and to the bottom of the class and remove the logic that you’ve placed in Write-Line. Your PrintfileErrorMsg class should look like this:

    //
    //
    // PrintfileErrorMsg
    //
    //
    class PrintfileErrorMsg extends ErrorMsg
        implements WriteLineInterface {

         private static int outputLineSize = 80;
         public int linesToSkip = 0;
         private int charsToSkip = 0;
    // Create a version of this variable that hides the one in TextMessage.
         public int msgSize;
    // Define a setErrorMsg method that establishes the number of
    // chars to output in order to center the error msg.
         public void setErrorMsg (String inputMsg) {
              super.setErrorMsg (inputMsg);
              charsToSkip = (outputLineSize - super.msgSize) / 2;
              this.msgSize = super.msgSize + charsToSkip;
              printLine ();
         }
    // Define a new method that prints this error message to the output.
    // It will be centered (based on the size of outputLineSize).
    // linesToSkip lines will be skipped first.
         public void printLine () {
              WriteLine.printLineWithPosition (getErrorMsg (),
                   linesToSkip, charsToSkip);
    // Print out the two msgSize variables.
              System.out.println ("this.msgSize = " +
                   this.msgSize + ", super.msgSize = " + super.msgSize);

             }
    }

  23. Save and compile this class:

    →   javac PrintfileErrorMsg.java.

  24. Now that you’ve made all of these changes, rerun the HelloWorld application. The output should look the same as it did before:

    Hello World!
    null
    Some Text
    Some Text
    SOME TEXT -> FRENCH TEXT
    Some Text for #2
    SOME TEXT FOR #2 -> FRENCH TEXT


    Some Text for #3
    this.msgSize = 48, super.msgSize = 16
    MsgSize for PrintfileErrorMsg = 48

Although the output looks unchanged, the program is in fact very different. You’ve started to build an infrastructure that will support the ability to print output lines in a standard yet flexible manner. The WriteLineInterface definition, and the supporting WriteLine class, begin to provide the infrastructure necessary to manage report creation properly.

For example, this infrastructure can be extended to support standard pagination functions when every line is printed. These pagination functions might include the ability to print standard heading information as necessary at the beginning of every page, as well as other page management functions.

For the last exercise, you will experiment with the concept of polymorphism. So far, two classes, ErrorMsg and PrintfileErrorMsg, contain a method named setErrorMsg. The one defined in PrintfileErrorMsg is an overridden version of the method defined in ErrorMsg. You will create a third version of this method in an unrelated class called Diagnostic. You will then have HelloWorld call each of these methods and examine what happens as a result.

  1. Create a new java source file in the C:java4cobol directory named Diagnostic.java. Insert these lines of text into the class file definition:

    import java.util.*;
    //
    // Diagnostic
    //
    //
    public class Diagnostic {
    // Define a setErrorMsg method. This method will write an error message to
    // the system diagnostic output.
         public static void setErrorMsg (String inputMsg) {
    // Print a banner.
              System.err.println ("====== A serious error has occurred ====
                == ");
              Date today = new Date();
              System.err.println (today);
              System.err.println ();
    // Print the message.
              System.err.println (inputMsg);
    // Print a banner end.
              System.err.println ();
              System.err.println ();
              Thread.dumpStack();
              System.err.println ();
              System.err.println ("====== End of serious error message ===
                ==");
         }
    }

  2. Save and compile this class:

    →   javac Diagnostic.java

  3. Edit the HelloWorld class so that it uses this new class. Using the text editor, open the HelloWorld.java source file and add these bolded lines of code to the bottom of the class:

    // Print this data member in PrintfileErrorMsg.
         System.out.println ("msgSize for PrintfileErrorMsg = " +
              myErrorMsg3.msgSize);
    // Experiment with polymorphism.
         System.out.println ("---------- Experiment with polymorphism --------
             --");
    // Create an error message String, and pass it to each of these
    // setErrorMsg functions.
         tempMsg = "Display this message";

         System.out.println ();
         System.out.println ("~~~~~~~ setErrorMsg in ErrorMsg does this: ~~~~
            ~~~");
         myErrorMsg.setErrorMsg (tempMsg);

         System.out.println ();
         System.out.println ("~~~~~~~ setErrorMsg in PrintfileErrorMsg does
              this: ~~~~~~~");
         myErrorMsg3.setErrorMsg (tempMsg);

         System.out.println ();
         System.out.println ("~~~~~~~ setErrorMsg in Diagnostic does this : ~~
             ~~~~~");
         Diagnostic.setErrorMsg (tempMsg);
    ...

  4. Save, compile, and rerun the HelloWorld application. The output should look like this:

    ---------- Experiment with polymorphism ----------

    ~~~~~~~ setErrorMsg in ErrorMsg does this: ~~~~~~~

    ~~~~~~~ setErrorMsg in PrintfileErrorMsg does this: ~~~~~~~
                   Display this message
    this.msgSize = 50, super.msgSize = 20

    ~~~~~~~ setErrorMsg in Diagnostic does this : ~~~~~~~
    ====== A serious error has occurred ======
    Fri Apr 16 18:08:50 PDT 1999

    Display this message


    java.lang.Exception: Stack trace
        at java.lang.Thread.dumpStack(Thread.java:983)
        at Diagnostic.setErrorMsg(Diagnostic.java:27)
        at HelloWorld.main(HelloWorld.java:69)

    ====== End of serious error message     =====

REVIEWING THE SAMPLES

Image

Let’s review the changes you’ve made. Try to relate the sample source statements to the result (for example, the output) each statement creates. If necessary, rerun the samples or look at the complete source code for this exercise on the CD-ROM. Feel free to experiment by yourself.

  • You first created a new class called TextMessage. You removed some of the data members from ErrorMsg and placed them in TextMessage.

  • Since the data members msgText and msgSize now belong to TextMessage, you defined a setMsgText method in TextMessage in order to set these variables.

  • You placed a new method named getTranslation in TextMessage. This method examined the static variable named LANGUAGECODE.

  • The ErrorMsg class was adjusted to inherit from this new class. Since ErrorMsg inherits from TextMessage, the public members and methods in TextMessage are automatically available to any class that creates an ErrorMsg class. In the example, HelloWorld creates an instance of ErrorMsg and can access these variables as if they are part of ErrorMsg. ErrorMsg and TextMessage share these variables.

  • When you set the static variable LANGUAGECODE to F, all instances of TextMessage simulated a translation into French. Instances of ErrorMsg (which are inherited from TextMessage) also exhibit this behavior.

  • You created a new class PrintfileErrorMsg. It inherits from ErrorMsg.

  • The statement import java.util at the beginning of the class tells the compiler to introduce the definitions for the java.util classes into the Diagnostic class. You needed to do this to use the Java Date class in the code.

  • You introduced a data member named msgSize in the class PrintfileErrorMsg. This variable hid the similarly named variable in the class TextMessage from HelloWorld, that is, when HelloWorld performed the following statement:

    // Print this data member in PrintfileErrorMsg.
         System.out.println ("msgSize for PrintfileErrorMsg = " +
               myErrorMsg3.msgSize);

  • Before the change, the variable in TextMessage was printed. After the change, the variable in PrintfileErrorMsg was printed.

  • You constructed an interface definition (WriteLineInterface) and defined a supporting class (WriteLine). You then used this class to simplify the logic in PrintfileErrorMsg. In a real system, any other class could also use the Write-Line infrastructure in order to centrally manage printing functions.

  • You demonstrated how polymorphism can be used. You sent the same message (setErrorMsg (String)) to instances of three different classes (ErrorMsg, PrintfileErrorMsg, and Diagnostic). Each of these classes respond to the message in a unique way:

    • ErrorMsg simply stored the String and did not print any text.

    • PrintfileErrorMsg printed the message after skipping some lines and centering the String in the print line. PrintfileErrorMsg also printed the values in the two msgSize variables.

    • Diagnostic printed the message and then performed a standard Java function to print the current call stack. This function would be very useful in a production system for recording contextual information appropriate for “postmortem” analysis of application or system failures.

  • You used a new object in the System class named err. This object is similar to the System.out object you have been using all along, except it is designed to write error messages instead of standard, or informative messages. Both objects will write to the display device by default.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset