5.4 Programming to a Promise

 < Day Day Up > 



5.4 Programming to a Promise

5.4.1 Programming to a Fact

Most programs written by novice programmers are developed with all the classes necessary for the program completely developed. We will call this programming to a fact because all the objects are facts; they exist when the program is written. For example, the Person class defined in Exhibit 3 (Program5.2) is used in the PersonTable class as the type for the array that stores the objects in the table, as well as for the type of parameters for the add method to make sure that only Person objects are added to the table. Because all objects stored in the table are Person objects, and all Person objects have a function "print," the programmer can program the loop in the printAll method that calls the print method for each object.

Exhibit 3: Program5.2: PersonTable Object That Stores Only Person Records

start example

 class Person {   private String name;   public Person(String name){     this.name = name;   }   public void print(){     System.out.println("My Name is "+ name);   } } public class PersonTable {   private Person people[];   private int currentSize;   public PersonTable(int size) {     people = new Person[size];     currentSize = 0;   }   public void add(Person person) {     people[currentSize] = person;     currentSize = currentSize + 1;   }   public void printAll(){     for (int i = 0; i < currentSize; ++i)       people[i].print();     }   public static void main(String args[]) {     PersonTable table = new PersonTable(10);     table.add(new Person("Benoit"));     table.add(new Person("Roget"));     table.printAll();   } } 

end example

Because all of the objects and code exist when the PersonTable and Person objects are created, the programmer can follow the execution through every line of code from the start of the program until it finishes. However, this advantage comes at the price of flexibility. The table that is created can only store Person objects, which is fine when everything for the program is written specifically for that program and is used only once in that program.

At some point, another instance of the table might be needed where objects of type Car will be stored instead of objects of type Person. The easiest and perhaps most common way in which the new program can be created is through reuse by copying, which involves copying the old program and modifying it to create a new program. For instance, a CarsTable program can be created by changing the references to Persons to Cars.

While this works, it has a number of disadvantages:

  • It leads to a large number of classes (and source programs) that must be stored, tracked, and maintained.

  • It is error prone, as a error could be introduced while changing the program to work for Cars.

  • If at a later time an error is found in a table, that change must be migrated to all the other copies of that table.

  • Only objects of one class (e.g., either Persons or Cars) can be stored in any one table; this is both a disadvantage and advantage, as we shall see later.

Generic objects can be developed to work in a number of different programs and to use multiple types of objects. What the generic object does and how it does it remain the same, but the component is able to do these operations on a number of different types of objects. In the case of the PersonTable, a more generic form would allow the table to store objects of any type, as long as they have a print method.

5.4.2 Implementing and Using Promises

One way to improve reuse by copying is to abstract out the behavior of the objects we wish to store (in this case, the ability to be printed) and write the generic object so that it can store any object as long as it contains the abstracted behavior. This can be thought of as the objects stored in the table making a promise to implement a method and the component then writing its methods to take advantage of the promises that will be made, referred to as programming to a promise.

The concept of programming to a promise is illustrated using the PrintTable class in Program5.3 (Exhibits 4 through 8). The first step is to define the promise, which is that the objects stored will have a print method. The actual classes for the objects that are stored are not defined when the PrintTable is written. These objects and their print methods are totally separate from the PrintTable and could be many different actual types as long as they make the promise to implement a print method. The promise to define a method in Java is called an interface. An example of the interface used to implement the Printable promise used by the PrintTable is shown in Exhibit 4 (Program5.3a).

Exhibit 4: Program5.3a: Printable Interface

start example

 public interface Printable {   public void print(); } 

end example

Applying the suffix "-able" to the verb that describes their functionality often generates names for interfaces. Here, the name Printable implies that the objects implementing the interface have a print method. This convention applies to many, but not all, of the interfaces in the Java API, such the interfaces Runnable and Serializable.

The PrintTable class is now implemented, as shown in Exhibit 5 (Program5.3b). It is almost the same as the PersonTable in Exhibit 3 (Program5.2), except that the PrintTable is not defined for a particular data type, but for any data type that is a Printable. So, everywhere that the Person data type was used in Exhibit 3 (Program5.2), the Printable interface is used. This tells the compiler to enforce the following rules:

Exhibit 5: Program5.3b: PrintTable Class

start example

 public class PrintTable {   private Printable printableArray[];   private int currentSize;   /**    *  Public constructor. The size of the array to allocate    *  (table size) must be passed in, as there is no default    *  table size.    */   public PrintTable(int size) {   printableArray = new Printable[size];   currentSize = 0;   }   /**    *  Add a Printable object to the table.    */   public void add(Printable a) {   printableArray[currentSize] = a;   currentSize = currentSize + 1;   }   /**    *  Print all objects stored in the table. Note that we know    *  that they are all Printable, so we know that a Print    *  method will be associated with each object.    */   public void printAll(){     for (int i = 0; i < currentSize; ++i)     printableArray[i].print();   } } 

end example

  • Any object parameter passed to the add method is an instance of a class that has implemented the Printable interface.

  • An object stored in the printableArray is an instance of class that has implemented the Printable interface.

Because any object stored in the printableArray is an instance of a class that has implemented Printable, the objects stored will have to make a promise to have a print method to be stored in that array, and that promise can be enforced by the compiler, as we will see later. Thus, a loop can now be written in the printAll method that calls the print method on objects stored in the printableArray. What are the data types of these objects? It is not known at this time, only that the data types will be classes that implement Printable. So where are the print methods? Once again, they will only be known when the objects are defined. The next step in this process of programming to a promise will define those objects.

The objects to be stored in the PrintTable can now be defined. These objects have only two requirements: (1) the class implements the Printable interface, and (2) a print method is included in the class. Because these requirements can be true for many different types of objects, the PrintTable itself can be used in many different programs to store objects of different types. Two example programs are given here. Exhibit 6 (Program5.3c) uses the PrintTable to store objects of type Person, and Exhibit 7 (Program5.3d) uses it to store objects of type Cars.

Exhibit 6: Program5.3c: Table That Stores Person Objects

start example

 public class Person implements Printable {   private String name;   public Person(String name){     this.name = name;   }   public void print(){     System.out.println("My Name is "+ name);   }   public static void main(String args[]) {     PrintTable T1 = new PrintTable(10);     T1.add(new Person("Benoit"));     T1.add(new Person("Roget"));     T1.printAll();   } } 

end example

Exhibit 7: Program5.3d: Table That Stores Car Objects

start example

 public class Car implements Printable {   private int engineSize;   public Car(int engineSize){     this.engineSize = engineSize;   }   public void print(){     System.out.println("My engine size is "+ engineSize);   }   public static void main(String args[]) {     PrintTable T1 = new PrintTable(10);     T1.add(new Car(320));     T1.add(new Car(150));     T1.printAll();   } } 

end example

An important advantage of these interfaces is that they ensure that the program is safe. The PrintTable class can be checked to verify that it does not call methods that do not exist, as only methods defined in the Printable interface can be used. There is no need at this point to know what the actual data types of the stored objects are because the PrintTable is written to work with the Printable interface. Later, when the objects to be stored are created, they must implement the Printable interface, which ensures that they have a print method. That the class for the object implements the Printable interface and that the print method is defined for classes that implement the Printable interface can be checked by the compiler.

5.4.3 Some Notes on Interfaces

It is important to note that interfaces do not define any data or implement methods. If the design requires that some data or methods must be defined, this can be accomplished using abstract classes, which are covered in Chapter 11. The reason for this is twofold, the first is practical and the second is semantic. The first reason why Java does not allow interfaces to define data or implement methods is that a class can implement multiple interfaces. If a method is defined in more than one implemented interface, it does not cause any problems, as the class that implements the interfaces implements that method once and it satisfies the requirement for all the interfaces. However, if multiple classes have been extended that define data with the same identifier or implement methods with the same signature, then the compiler must have a way to choose which identifier or method is meant when the identifier or method is accessed. This is a complex problem in languages that allow multiple inheritance and one that Java avoided.

The second problem with defining data or implementing methods in an interface is semantics. An interface is simply a "promise" that an implementing class will implement some behavior via method calls. It does not, and should not, put any constraints on that class as long as it provides a valid method call to satisfy its promise. Any functionality built into an interface forces the implementing class to do something in a specific manner, which violates the principal of the interface simply providing a promise.

One result of interfaces not defining data or implementing methods is that it does not make sense to try to instantiate an interface. For example, the following code would be invalid:

   Printable p = new Printable();   p.print(); 

Because Printable does not define a print method, only a promise that an object implementing Printable will create a print method, there would be no print method to call if an interface could be instantiated. If the "object" p is added to the printArray, a call to its print method would obviously be invalid. This violates the safety guaranteed by the interface. Thus, it does not make sense to allow interfaces to be instantiated, and it is not allowed in Java.

This example shows what we mean by programming to a promise, or more appropriately programming to an interface. If we write a component (in this case, an object that stores and prints a table of objects) using interfaces instead of classes, it can be used for any objects that will implement that interface. This principle of using interfaces to define components is used throughout the rest of the book and forms the basis for the Java Event Model used in the AWT, and for most distributed programming paradigms. It also provides the basis for isolating classes in a program and producing more robust and reusable objects.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net