Interfaces are a Java construct that make programming powerful applications easy. Designing to an interface is a strategy that all object-oriented programmers should employ, but Java is the only language that makes the process easy. This article will show why Java interfaces allow our Java classes to be flexible, extensible, and pluggable.
There is a new strategy in object-oriented programming; its called designing to an interface. The idea of this strategy is to forget about using complex hierarchies of classes and to focus on the common interfaces of classes. (Remember that a classs interface is its list of publicand protectedfunctions.) With inheritance, the interface of a base class as well as its implementations are passed on to its subclasses. However, Java classes dont always share the same functions and implementations of other classes. When your classes share functions of the same name but not necessarily the same implementations of other classes, using an interface can simplify your programming efforts.
When you design to an interface using Javas inheritance, you write your derived classes to use the functional interfaces of your base classes. Javas polymorphism (Ill define this term later in the article) then kicks in to call the proper subclass implementation of the base classs interface. If you feel as though youre pushing your understanding of object-oriented programming right now, its OK because Java has an alternative approach, designing to an interface, that is more intuitive than inheritance. This article covers the approach to designing to an interface, first with Javas intuitive method and then, after introducing you to the concepts of polymorphism, with the inheritance method.
Java has a construct that supports designing to an interface more clearly than any other object-oriented language. Even with the sophisticated language of C++, programmers have to bend over backwards to simulate what Java does effortlessly. This Java construct is aptly called an interface. 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.
The ITransaction interface in Figure 1 is composed of functions typically associated with transactions, such as setting and getting the date, quantity, cost, and description of a transaction. At a first glance, youd think that an interface is pretty much the same thing as a class. But if you look more closely, youll notice that the ITransaction interface contains no code implementations for its functions. You might also notice that the interface does not declare the date, description, cost, or quantity fields that its functions presumably get and set.
A Java interface is a standard protocol that is used as a consistent API for behaviors that are common to a variety of classes. For instance, as you design an object model, you may find that you are defining the same printing functions in classes that represent dissimilar entities. So, you develop an IPrint interface such as the one shown at the top of Figure 1. That interface then serves two purposes: First, it defines the protocol for the implementation of a printable interface. Second, any object that has an implementation for the printable interface can be used as a runtime IPrint object via polymorphism. Theres that 10-dollar word again, and I promise Ill cover it, but first I need to show you how to implement an interface.
You can see in Figure 2 that the purchase order (POActivity) class implements the ITransaction interface. When you design a class that implements an interface, you must provide code implementations for all of the functions inherited from that interface. As a result, the POActivity class has code for all of the functions declared in the ITransaction interface. Note that to implement the ITransaction interface, the POActivity class had to define functions with the exact name, return type, and number and type of argumentsthat is, the same signature as those of the ITransaction interface.
If you look at the Unified Modeling Language (UML) diagram in Figure 3, youll notice that the POActivity class declares those date, description, quantity, and cost fields that the functions of the ITransaction interface implied. It may seem to you that it would have been clearer had I put those four fields directly in the ITransaction interface. But Java interfaces dont support the declaration of fields. And, when you think about it, if you could put fields in an interface, it would be as if you were defining an entity (that is, a real- world object that has a state and identity). An interface is not an entity; it is only a list of functions, merely a protocol that is a standard way of communicating. Because the creators of Java did not want interfaces to represent entities, an interface can never be instantiated as an object. You couldnt, for example, code the following:
ITransaction trans = new ITransaction(); Actually, I lied. You can declare a field in a Java interface, but that field is implicitly qualified as static and final. (Note that the fields and the functions of an interface are also implicitly public.) The static and final qualifiers effectively make the fields of an interface constants because their values cannot be modified; they are immutable. Because fields of an interface are immutable, their values must be initialized as a part of its definition as the class field called RED in the IColor interface of Figure 4. Realize that, although the IColor interface has fields, IColor still does not represent an entity because the fields are constants. Youll find that the most common use of interface fields is for default values.
Inheritance Versus Interfaces
The Java ITransaction interface of Figure 1 could have been developed instead as a class called Transaction, and that class could then have been a superclass for the work order (WOActivity) and purchase order (POActivity) classes. But as the object model in Figure 3 shows, the WOActivity class already has a base class. Java classes can only have one parent, so WOActivity couldnt extend a Transaction class. Even so, WOActivity does implement two interfaces: ITransaction and IPrint. A big advantage of interfaces over inheritance is that, although a class can extend only one parent class, a class can implement multiple interfaces. The UML notation, by the way, for implementing an interface is a dotted line, and an interface is differentiated from a class with the word interface at the top of a box.
If you look at the Java code for the WOActivity classs implementations of the ITransaction interface in Figure 2, youll notice that is varies from those of the POActivity class. (Note that the integer representations for badge would probably have been implemented in a real system by an Employee class that encapsulates the attributes and behaviors of an employee.) Thats another reason to use interfaces rather than inheritance: When implementations of an interface are going to vary from class to class, it doesnt make sense to put code implementations for those functions in a base class. If you remember from the most recent article of this series [see Object-oriented Design for AS/400 Java Applications: Inheritance, MC, October 1998], Java does allow you to override the implementation of a function inherited from a parent class, but inheritance works best when the code implementation of a base class fits the requirements of its subclasses.
Whats the Big Deal?
The ability to define a standard protocol with a Java interface is all well and good, but that ability alone is nothing more than an enforced naming convention. The big deal comes from the flexibility, extensibility, and pluggability that interfaces give us. Interfaces are flexible because they allow you to change the implementation of an interface based on varied requirements of classes. As I will explain, they are extensible because you can string multiple interfaces in the definition of a class. And they are pluggable because, as I will show you, the classes that implement the same interface can be used interchangeably.
Extensibility
Complex interfaces with lots of functions can often be simplified by dividing them into several small interfaces. The ITransaction interface of Figure 1, for instance, could be further divided into IDate, IDescription, IQuantity, and ICost interfaces. An entirely new- looking (but functionally equivalent) ITransaction interface is shown in Figure 5 that is a composite interface of the four components of the ITransaction interface. This variety of interfaces also serves to further protect the internals of objects by giving other objects access only to a specific interface.
Pluggability
It follows that a class (such as the WOActivity class) that implements an interface can then be instantiated as an object. That object can then invoke the functions that were implemented in that class but defined in the interface:
WOActivity woActive = new WOActivity(); woActive.setDescription(Widget polished); The pluggability of the interfaces of WOActivity will become evident to you when you see that you can assign the reference to that WOActivity object to a variable that is typed as an interface:
ITransaction trans = woActive; Earlier, I said that you can never instantiate an interface. You can, however, declare the type of a variable as an interface and assign to that interface variable a reference to any object whose class implemented that variables interface. The interface variable may then invoke the functions that were defined in its interface. For instance, the trans interface variable can invoke setDescription, but it cannot invoke printHeader. Thats because, even though printHeader is a part of the WOActivity class that the woActive object was originally instantiated as, printHeader is not a part of the ITransaction interface. The trans variable, because it is constrained by the functions of the object type of its ITransaction object type, cannot invoke the printHeader function.
It may sound as if interface variables are used to limit the scope of objects, but they actually make Java more powerful. As I will show you, the ability to reference objects with an interface variable makes objects that implement interfaces very pluggable.
The Many Faces of an Interface
The TestTransaction application class of Figure 6 illustrates the pluggability of interfaces. The PrintTransaction utility class illustrates polymorphism (I told you Id define this term).
Polymorphism means, literally, many changes. As Ill explain, the function implementations of the ITransaction interfaces go through a metamorphosis several times in the PrintTransaction class. TestTransactions main function bootstraps the application by creating itself by invoking its constructor function with Javas new operator. The constructor function, TestTransaction, creates a POActivity object and invokes its implementation of the setDescription function inherited from the ITransaction class.
Then, TestTransaction creates a WOActivity object and invokes its implementations of several of the inherited functions of the ITransaction class. So far, no new Java strategies have arisen, but TestTransaction then creates two ITransaction variables and sets them to reference objects whose class implemented the ITransaction interface. Remember, you cannot instantiate an interface as an object:
ITransaction trans = new ITransaction(); // compile error However, you can assign a reference to an object that was created from a class that implemented the same interface as a variable typecast as that interface:
POActivity po = new POActivity(); ITransaction trans1 = po; This process is sometimes called downcasting because the object referenced with the variable po was typecast as member of the POActivity class, but because the POActivity class also is a type of ITransaction, we can assign that objects reference to an ITransaction variable. Because the trans interface variable was less than that of a POActivity class variable, you can say that we downcast a POActivity variable to an ITransaction variable.
The TestTransaction function then prints the description of the transaction using the interface variables. It would have been easier to have simply invoked the getTransaction functions directly from the wo or po variables:
System.out.println(wo.getDescription()); But by using the interface variables, I have given you a hint of the power of interfaces and the polymorphic behavior of objects. You now can see that, although the trans1 and trans2 variables are not of the POActivity or WOActivity class, they invoke the functions that were implemented in those classes.
The last three Java statements of the TestTransaction function further illustrate the polymorphic behavior of interfaces. The printGroup variable is an array that is typecast as IPrint. The array is initialized to reference the two POActivity and WOActivity objects. Then a PrintTransactions object is instantiated. Note that the constructor for PrintTransaction opens the selected output medium. The printGroup array is then passed to PrintTransactionss print function, which spins through the array and invokes each of the functions of the IPrint interface.
The PrintTransaction class and its print function have been designed to an interface. Rather than your writing a function to print work-order transactions, and then writing a function to print purchase-order transactions, and then writing, well, you get the ideayou write one function that is designed to use the IPrint interface. The PrintTransaction class can handle the printing of any class that implements the IPrint interface. Figure 7 shows the result of running the TestTransaction application.
The Power of Interfaces
Even if you never design a single Java interface, you will, nonetheless, be using them. Java interfaces are heavily used in the implementation of Java itself. Interfaces are used in two areas in particular: Javas GUI event-handling mechanism and Javas Remote- Method-Invocation (RMI) facility.
Java interfaces are also heavily used in many of the standard Java packages, such as the Abstract Windowing Toolkit (AWT). When you develop a Java GUI with AWT, you group window components such as buttons, text boxes, and list boxes into a window. Each of these components can generate a variety of events, such as the button being clicked or text being entered into the text box or an element of the list box being selected with the mouse. Each of these events has an associated AWT interface.
You design your Java application to handle these events by creating classes that implement the standard AWT interfaces. Object instances of the classes that implement the AWT interfaces are then registered with a component so that they can react to events generated by that component. You dont code calls to those functions; the Java Virtual Machine (using polymorphism) automatically does that for you by invoking your classs code implementation of the AWT interface. Because these interfaces are common, you can obtain Java classes that implement these standard interfaces from a wide variety of sourcesother teams, Internet freeware, and third-party suppliersonce again displaying the pluggability of interfaces.
Another example of the power and versatility of interfaces is RMI, which is Java
1.1s strategy for distributed computing. Remote access to objects residing on host machines is enabled through an interface. You pull the functions of a host class that require remote access into a Java interface. Then, with the magic of RMI, a Java application on a client machine can declare a variable to be of the interface type and assign to that variable a reference to the object on the host machine that implements the interface.
The other day, my wife said to one of our sons: Thats what you get for being a member of my side of the family. She laughed and quickly corrected her statement because it sounded as if she were saying that my son had inherited attributes only from her. But I thought, hey, thats Java inheritanceonly one parent. What, then, would be the human analogy to Java interfaces?
Human behavior allows us to interface with others using such qualities as character, style, and skill. These qualities are not inherited; they are acquired: You provide your own implementation of character, style, and skill. If my sons (who are trying to develop a class of their own) were adequately familiar with Java and would actually listen to me, my directions to them would be to implement a character, implement a style, and implement a skill (Figure 8). They already have the inherited attributes of intelligence and creativity, as well as self-preservation and desire. I could tell them to develop multiple interfaces so that, like a Java class, they can have many interfaces and others would be able to interface with them in a variety of ways to satisfy their particular needs.
For instance, I have my own implementations for the IProgrammer, IWriter, or IMakeSandwiches interfaces. When my sons use my IMakeSandwiches interface, they have only one need in mind. In other words, they dont care about my IProgrammer implementationthey just want a good sandwich.
// the IPrint interface public interface IPrint {
// note, all functions of an interface // are implicitly public void printHeader(); void printDetail(); void printFooter();
}
// The ITransaction interface import java.util.Date; public interface ITransaction { Date getDate(); String getDescription(); int getQuantity (); BigDecimal getCost (); void setDate(Date date);
void setDescription(String desc); void setQuantity (int qty); void setCost(BigDecimal cost);
}
Figure 1: The transaction and print interfaces, as with all Java interfaces, have no code implementations
public class POActivity extends rdbPOActivity implements ITransaction {
private java.util.Date date;
private String description;
private int quantity;
private String shipMethod;
public java.util.Date getDate() {return date;}
public String getDescription() {return description;}
public int getQuantity() {return quantity;}
public void setDate(Date date) {this.date = date;}
public void setDescription(String desc) {description = desc;}
public void setQuantity(int qty) {quantity = qty;}
public void setShipMethod(String shipMethod) {this.shipMethod = shipMethod;} }
public class WOActivity extends rdbWOActivity implements ITransaction, IPrint {
private java.util.Date date;
private String description;
private int quantity;
private int shift;
private int badge;
// ITransaction interface:
public java.util.Date getDate() {return date;}
public String getDescription() {
return "badge no: "+badge+ " shift: "+shift+" "+description;
}
public int getQuantity() { return quantity;}
public void setBadge (int badge) { this.badge = badge;}
public void setDate(Date date) { this.date = date;}
public void setDescription(String desc) { description = desc;}
public void setQuantity(int qty) { quantity = qty;}
public void setShift (int shift ) { this.shift = shift;}
// IPrint interface
public void printHeader() {}
public void printDetail(){}
public void printFooter(){} }
Figure 2: The POActivity and the WOActivity classes both implement the transaction interface
}
interface IDescription {
String getDescription(); void setDescription(String desc); }
interface IQuantity { int getQuantity (); void setQuantity (int qty); }
interface ICost { void setCost(BigDecimal cost); BigDecimal getCost ();
}
interface ITransaction extends IDate, IDescription, IQuantity, ICost {} Figure 5: Interfaces can be combined into one composite interface
public class TestTransactions {
public TestTransactions () {
POActivity po = new POActivity();
po.setDescription("purchase order shipped");
WOActivity wo = new WOActivity();
wo.setDescription("W/O routing step 110 completed");
wo.setDate(new java.util.Date());
wo.setBadge(928);
ITransaction trans1 = po;
ITransaction trans2 = wo;
System.out.println(trans1.getDescription());
System.out.println(trans2.getDescription());
IPrint[] printGroup = {po, wo};
PrintTransactions print =
new PrintTransactions(PrintTransactions.FILE);
print.print(printGroup);
}
public static void main(java.lang.String[] args) {
new TestTransactions();
}
}
public class PrintTransactions {
public void print(IPrint[] printGroup) {
for (int i = 0; i < printGroup.length; i++) {
printGroup[i].printHeader();
printGroup[i].printDetail();
printGroup[i].printFooter();
}
}
}
Figure 6: Javas polymorphism adapts code at runtime to invoke the various implementations of interfaces by dissimilar objects
purchase order shipped Mon Jun 15 21:23:27 EDT 1998 Badge: 928 Desc: W/O routing step 110 completed Purchase Order Activity:
purchase order shipped
*** POActivity Footer ***
Work Order Activity: Mon Jun 15 21:23:27 EDT 1998 Badge: 928 Desc: W/O routing step 110 completed *** WO Footer *** Figure 7: The Java Virtual Machine, via polymorphism, dynamically handles the behavior of various implementations of the ITransaction interface
class Denoncourt { int intelligence, creativity; // [editorial license] }
class DonDenoncourt extends Denoncourt implements ICharacter, IStyle, IProgrammer, IWriter, IMakeSandwiches {
...// implementations of interfaces }
class JoshuaDenoncourt extends Denoncourt implements ICharacter, IStyle, IArtist, IWriter {
// implementations of interfaces:
...
// note that JoshuaDenoncourt's implementation of character, style and writer // are completely different from DonDenoncourt's, even if the interface is the // same. JoshuaDenoncourt also implements an artist interface
...
// interfacing with others, // JoshuaDenoncourt usually uses the DonDenoncourt implementation // of IMakeSandwiches to get a sandwich but he also uses // Subway or other IMakeSandwiches implementations
void getSandwich(IMakeSandwiches chef) { chef.getSandwich();
}
}
Figure 8: Individuals may inherit characteristics from their parents, but to interface with the world, they have to implement their own character, style, and skill
LATEST COMMENTS
MC Press Online