This article, the fifth and final installment on object-oriented design with Java, introduces you to abstract base classes and polymorphism. Learn when and how to use abstract classes and how Javas polymorphism kicks in to make it all work. This article explains how the combination of abstract base classes and polymorphism will provide you with coding strategies that the RPG programming language will probably never provide.
In the previous article in this series, I introduced you to the power of Java interfaces (see Object-oriented Design for AS/400 Java Applications: Java Interfaces, MC, November 1998). Java interfaces allow you to associate a set of function names that serve a similar or related purpose into a discretely named unit. The difference between a Java interface and a Java class is that the functions of an interface are without implementations whereas the functions of a class must have code implementations. Java interfaces work well when dissimilar entities exhibit a common behavior. You design a Java interface as a list of functions that represent common behaviors across dissimilar entities. You design Java classes to represent business entities. Each of these Java classes provides custom code implementation for the functions of its common interface. The idea is that classes of an application have a consistent interfaceits just that the implementations of these interfaces vary from class to class.
But what do you do when you find yourself repeatedly coding the same exact implementation for the functions of an interface? You might consider using a base class instead. Perhaps you might want to define some nonconstant fields (which interfaces do not support), so, once again, you consider developing a base class. I covered base classes in the third article of this series (see Object-oriented Design for AS/400 Java Applications: Inheritance, MC, October 1998). A base class allows you to provide functions with code implementations for the general behaviors of a common group of entities. An example of a
base class might be a customer. That base class is then extended by a derived class to provide functions that are specific to the behaviors of a subset of that group of entities. Examples of classes derived from the customer class might be consumer and company class. Both of these classes inherit much of their attributes and behaviors from their common customer base class, but they both also have extended the base functionality of ancestry to provide behaviors specific to consumers or companies.
Heres the rule of thumb for deciding when to use a base class or when to use an interface: Use a base class when the common functions can share the same implementation; use an interface when the common functions cannot share the same implementation. But what do you do when you have a mix (that is, a mix of functions, some of which should have base class code implementations, and some of which should be custom coded in implementations of the derived class)?
Abstract Classes
Javas solution to this design dilemma is an abstract class. Java abstract classes are like Java interfaces in that they can define functions without providing implementations. An abstract class, like an interface, can never be instantiated. Unlike interfaces, however, abstract classes can optionally provide function implementations. Its similar to your boss giving you a list of things for you to do: Some of them he wants done his way; the others, he doesnt care how you do themhe just wants you to get them done. One other difference between abstract classes and interfaces is that an abstract class can have fields that are mutable (changeable).
A simple but very real example of an abstract class is Javas own Number class (shown in Figure 1). I covered the Number class in my article on inheritance, but I neglected to mention that it was an abstract class. The Number class is a generalization of numbers, any numberbe it integer, long, short, floating point, or fixed decimal. Some of the functions of the Number class have implementations and others do not, as they are abstract functions. The long and the short of the Number class is that many Java classes derive from the Number class. Each of these classes defines implementations of the abstract functions of the Number class that are appropriate for the manipulation of the characteristics of that derived classs type of number. The BigDecimal class, for instance, is derived from the Number class to implement a fixed-point decimal class. Because the BigDecimal class extends the abstract Number class, it has to provide code implementations for the four abstract functions of the Number class (intValue, longValue, floatValue, and doubleValue). The two nonabstract functions of the Number class (byteValue and shortValue) do not have to be implemented in the BigDecimal class. If you create a BigDecimal object, that object has shortValue and byteValue functions; its just that the code implementation for these functions is provided by BigDecimals parent Number class.
The Transaction class shown in Figure 2 illustrates an abstract class. Transaction implements the ITransaction interface in the design of a base transaction class. Notice that the class definition begins with the Java keyword abstract. The abstract qualifier states that this class is not to be considered a concrete data type. You cannot instantiate an abstract class; it is only to be used as a base class. You couldnt instantiate a Number object, for example, with new Number() because Number is an abstract class. When a class that is derived from an abstract class implements the null functions (functions that have no code implementations; i.e., they provide only their name, arguments, and return type) of its base, that class is then considered a concrete class. So you can create a BigDecimal object (which is a Number) with new BigDecimal() because BigDecimal is a concrete class.
The Transaction class defines the mutable fields of date, description, quantity, and cost. You may recall that Java interfaces only support constant fields, so the ITransaction interface could not declare the four fields that are obviously part of any transaction. The Transaction class implements the ITransaction interface, so it defines those four fields. The Transaction class, however, is an abstract class because it has no implementations for one of its functions: getDescription. The getDescription function has the obligatory abstract
keyword qualifier to tell the compiler that, No, you did not want to provide code implementation for this function. Abstract classes are handy when some of the functions of an abstract class have general implementations that are usable by its derived classes, but the other functions would best be implemented in the derived classes themselves.
Think about the Transaction class: The getDate function implementation will work fine for any class derived from the Transaction base class. Those derived classes, however, clearly would want to provide their own description for their transactions. Note that when one or more functions of a class are declared as abstract, the class definition also must be declared as abstract. The Java compiler then makes sure that any derived classes implement those abstract functions and that the abstract class is not to be instantiated as an object.
The Transaction class defined in Figure 2 is used as the base for both the WOTransaction and POTransaction classes as shown in Figure 3. Besides extending the characteristics and behaviors of its Transaction base class with the badge field and setBadge function, WOTransaction and POTransaction both provide the required implementations for the abstract getDescription function. The work order and purchase order transaction classes also implement functions for the IPrint interface. You may have noticed that the Transaction class implemented the IPrint interface. You may also be wondering, then, why the Transaction class didnt provide its own implementation of the IPrint interfaces functions. After all, the Transaction class nicely provided implementations for all of the functions of the Itransaction interface! Abstract classes like WOTransaction are not required to provide code for any of the functions that have the abstract keyword specified. All the functions of an interface are implicitly tagged as abstract, and the responsibility of implementing the code for abstract functions is delegated to those classes that implement that interface. The Transaction classbecause it is an abstract classdoes not have to provide code for the functions of the IPrint interface; it effectively delegates the responsibility for implementing the IPrint interface to any classes that extend the Transaction classlike the WOTransaction class.
Polymorphism
The beauty of base classes, abstract base classes, and Java interfaces is that you can design other Java classes to use the general design of the functions of those base classes and interfaces. You wouldnt, for instance, create separate print classes for WOTransaction and POTransaction. You would create a generic print class. The PrintTransactions class of Figure 4 can print information about any object whose class implemented the IPrint interface (which includes the WOActivity and POActivity classes from my last article). The TestAbstractTransaction class of Figure 5 creates (instantiates, in object-oriented parlance) POTransaction and WOTransaction objects in its constructor. Both of these classes extend the abstract base class Transaction. The printGroup array, which holds objects of the Transaction class, is initialized to contain references to the po and wo objects. Then the TestAbstractTransactions constructor instantiates a PrintTransaction object. Note that the constructor for PrintTransaction is passed a constant valuea static final field from the PrintTransaction classthat tells the print utility class to print to a file. PrintTransactions print function accepts an argument of the array of printGroup Transaction objects. The PrintTransaction print function, by the way, does not take a reference to Transactions, nor does it take a reference to POTransactions or WOTransactions. The print function was designed to take an object whose class implemented the IPrint interface; the print function was designed to an interface. At runtime, PrintTransactions print function invokes the function of the class (that the printGroup object was originally created as) by using an object-oriented feature called polymorphism. The listing in Figure 6 shows that the PrintTransaction print function magically uses the appropriate implementations of the IPrint interface. (The code for the Java classes used in this article can be found at MCs Web site at www.midrangecomputing. com/mc/99/01.)
Dont Do Today What You Can Put Off Until Tomorrow
You may be wondering how all this polymorphism stuff works. Let me give you the two-minute explanation. You probably understand that with legacy programming languages like RPG, function calls are tied to their code implementation at compile-time (either by reference or by copy). Consider for instance, an RPG IV program where a driver module invokes another module. The code for that other module is bound to the program at compile-time. When using the bind-by-copy method, all of the code for the invoked module becomes an integral part of the compiled program. You would get the same effect if you copied the entire source of that module to within the driver module. With Java, function calls are not bound at compile-time; they are bound at runtime. When an object invokes a function, the Java Virtual Machine (JVM) uses the object reference and calls the function that the objects class implemented. The code for the function is dynamically bound at runtime.
When you design to an interface, you declare your variables to be of either an interface or a base class. Those variables are known as object references because they are handles to object instances of any object whose class had implemented the interface (if the variable is typed as an interface) or extended the base class (if the variable is typed as a base class). When you use an object reference to invoke a function, the code for that function is bound at runtime, rather than compile-time, to the function implementation of the class that was used to instantiate the referenced object. This is a difficult concept to grasp because it seems that if the function that uses that object knows it only as a base class or an interface, it would not know to invoke the function of a class. The secret is in the object reference. That reference is a handle to an object, and the object knows what class it was created as and, hence, the appropriate function to call. This runtime process does add overhead, which is one of the biggest reasons why Java (and other object-oriented languages) is slower than non-object-oriented languages such as RPG and COBOL. But the pluggability of Java interfaces, classes, and abstract classes provides additional benefits, the value of which go way beyond the cost of a few extra CPU cycles.
Twenty or 30 years ago, assembly language programmers were saying that third- generation languages like C, RPG, and COBOL consumed too many resources, but how many assembly language programmers do you know? We all do know, however, that the industry moved to third-generation languages because these languages made programmers more productive. The industry at large has already moved on again to object-oriented languages to raise programmers to yet a higher level of productivity. Now that Java provides us with a viable object-oriented business language, its time that AS/400 shops adopt the proven strategies of object-oriented design.
public abstract class java.lang.Number {
public abstract int intValue();
public abstract long longValue();
public abstract float floatValue();
public abstract double doubleValue();
public byte byteValue();
public short shortValue();
public java.lang.Number();
}
class java.math.BigDecimal
extends java.lang.Number {
// code omitted
public int intValue();
public long longValue();
public float floatValue();
public double doubleValue();
// code omitted
}
Figure 1: Javas BigDecimal class provides code implementations for the abstract functions declared in its parent class, java.lang.Number
import java.math.BigDecimal;
import java.util.Date;
public interface IPrint {
public void printDetail();
public void printFooter();
public void printHeader();
}
public interface ITransaction {
public Date getDate();
public String getDescription();
public int getQuantity ();
public void setDate(Date date);
public void setDescription(String desc);
public void setQuantity (int qty);
}
public class RdbEntity {
// DB2/400 create, read, update, delete functions
}
abstract class Transaction extends RdbEntity
implements IPrint, ITransaction {
protected Date date;
protected String description;
protected int quantity;
protected BigDecimal cost;
// no implementation for IPrint interface
// no implementation for new function
abstract public String getDescription();
// implementations for ITransaction interface
public BigDecimal getCost () {return cost;}
public Date getDate() {return date;}
public int getQuantity () {return quantity;}
public void setCost(BigDecimal cost) {
this.cost = cost;}
public void setDate(Date date) {
this.date = date;}
public void setDescription(String desc) {
this.description = desc;}
public void setQuantity (int qty) {
this.quantity = qty;}
}
Figure 2: The Transaction class is an abstract class because it does not provide code implementation for the abstract getDescription function
public class WOTransaction extends Transaction{
private int badge;
// implementation of abstract function
public String getDescription() {
return date+" Badge: "+badge+" Desc: " +
description;}
// implementation for IPrint interface
public void printDetail() {
System.out.println(getDescription());}
public void printFooter() {
System.out.println("*** WO Footer *** ");}
public void printHeader() {
System.out.println("Work Order Trans:");}
public void setBadge(int badge) {
this.badge = badge;}
}
public class POTransaction extends Transaction{
private String shipMethod;
// implementation of abstract function
public String getDescription() {
return " Ship via: " + shipMethod +
" Desc: "+description;}
// implementation for IPrint interface
public void printDetail() {
System.out.println(getDescription());}
public void printFooter() {
System.out.println(
"*** POTransaction Footer ***");}
public void printHeader() {
System.out.println(
"Purchase Order Transaction:");}
}
Figure 3: The WOTransaction and POTransaction classes provide code implementations for the abstract getDescription function of their base Transaction class and the IPrint interface
public class PrintTransactions {
public static final String FILE = "FILE";
public static final String PRINTER="PRINTER";
PrintTransactions (String printer) {
// create and open file here if "FILE"
// passed else write to printer
}
public void finalize() throws Throwable {
// flush file or printer
}
public void print(IPrint[] printGroup) {
for (int i = 0; i < printGroup.length; i++)
{
printGroup[i].printHeader();
printGroup[i].printDetail();
printGroup[i].printFooter();
}
}
}
Figure 4: The PrintTransactions class can print information about any object whose class implemented the IPrint interface
public class TestAbstractTransaction {
public TestAbstractTransaction ( ) {
POTransaction po = new POTransaction();
po.setDescription("purchase order shipped");
WOTransaction wo = new WOTransaction();
wo.setDescription("W/O routing step 110 completed");
wo.setDate(new java.util.Date());
wo.setBadge(928);
// create an array and initialize it to the
// po and wo Activity objects
Transaction[] printGroup = {po, wo};
PrintTransactions print =
new PrintTransactions(PrintTransactions.FILE);
// print is expecting an array of IPrint but since
// the abstract Transaction class implements
// the IPrint interface, polymorphism handles it.
print.print(printGroup);
}
public static void main(java.lang.String[] args) {
new TestAbstractTransaction();
}
}
Figure 5: The TestAbstractClass was designed to use the common interface of the Transaction abstract base class and the IPrint interface
Purchase Order Transaction:
Ship via: null Desc: purchase order shipped
*** POTransaction Footer ***
Work Order Trans:
Tue Nov 03 14:15:10 EST 1998 Badge: 928 Desc: W/O routing step 110 completed
*** WO Footer ***
Purchase Order Transaction:
Ship via: null Desc: purchase order shipped
*** POTransaction Footer ***
Work Order Trans:
Tue Nov 03 14:15:38 EST 1998 Badge: 928 Desc: W/O routing step 110 completed
*** WO Footer ***
Figure 6: The output of the TestAbstractClass application demonstrates the results of designing to an interface using both abstract classes and interfaces
LATEST COMMENTS
MC Press Online