CS2 Advanced Programming in Java note 5. Modular programming in Java:
Interfaces. This note reviews what is meant by modular programming, and looks
in ...
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
CS2 Advanced Programming in Java note 5 Modular programming in Java: Interfaces This note reviews what is meant by modular programming, and looks in detail at one of the main features which Java provides to help support modularity, namely the interface mechanism for classes. The other main part of the Java modules system, namely the package mechanism, will be considered in the next lecture note. 5.1 Modular programming As soon as a program reaches any size or significance, it becomes important to consider not just what is in it, but also how it is written. Can several people work on it at once? How can we be sure it does the things it is supposed to do? Can it be maintained by others? How difficult will it be to enhance in the future? These are properly issues of software engineering, but they also affect programming languages themselves. One small part of this is the notion of modular programming: separating a program into distinct modules, each self-contained and with well-defined links to other relevant modules. Ideally, we would like to do this in such a way that: Different people or teams can work on different modules independently. The modules are guaranteed to fit together smoothly at the end. Changes within a module will have only limited effects on others. The basic idea of modularity is a very general one, and most current programming languages include support for modules at various levels. In this note and the next, we will look at two basic ways of trying to achieve these ideals, and the features of Java that exist to support them. The first way is by means of explicit interface specifications for the modules; this is supported by the interface mechanism of Java, which we discuss in some detail in this note. The second way is by means of data abstraction or hiding; this can be achieved using the package mechanism of Java, which we will cover in the following note. In practice, it is often a good idea to use both of these mechanisms at once. 5.2 Interface specifications One way to help achieve modularity is to provide explicit descriptions of the interfaces between the various modules. Typically, at an early stage in the design 1
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
of the software, we would write out a clear specification of what each module is supposed to do. Such a description is called an interface specification for the module, and serves as a kind of agreement or “contract” between Programmer A (whose job it is to implement the module) and Programmer B (whose job it is to write the client code that makes use of the module). On the one hand, Programmer A knows that it is his responsibility to make sure his implementation of the module meets the specification. On the other hand, the specification tells Programmer B what properties of the module she can rely on when writing her code: her responsibility is to make sure that if the module meets the specification then her program will work. The interface specification will typically consist of a list of the operations that the module is required to provide. For instance, if there is to be a module for manipulating dates, we might specify that it has to be able to displays a given date in day/month/year format calculate the weekday of a given date calculate the date a certain number of days after or before a given date calculate the number of days between two given dates and so on. In a specification, the emphasis is on what capabilities the module provides rather than on how it provides them — this leaves Programmer A free to decide on the best way to implement the module. Furthermore, Programmer B can make use of the module only by using the operations listed in the specification, since that is all she knows about the module (thus, these operations form the interface to the module). A major strength of this approach is that if A later decides to modify his code slightly (for instance, to make it more efficient), then provided the new version still meets the specification, the overall system will still work — no changes to B’s code will be needed. 5.3 Interfaces in Java In Java, it is natural to use a class for each program module — a bunch of related operations acting on some data. The operations (i.e. methods) provided by the class can be listed in a Java interface, like this: interface Date { String toString () ; // gives the date in dd/mm/yyyy format String weekday() ; // returns first 3 letters of weekday, e.g. "Tue" Date addDays (int n ) ; // returns the date n days after this date int diff (Date d ) ; // returns the number of days from d to this date } This gives the names of some methods and some types associated with them, but doesn’t provide any code for the methods (in this respect interfaces are very similar to abstract classes). Notice that we might have hoped for more than this 2
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
— ideally, we would like an interface to specify in a precise way what the methods are supposed to do! — but this would lead us beyond what Java actually provides and into the rather different area of specification languages. As it happens, even the rather modest kinds of interfaces provided by Java turn out to be very useful. In the above example, information about what the methods are intended to do has been included in the form of comments. We may then write one or more classes that implement this interface, e.g.: class SimpleDate implements Date { private int day, month, year ; SimpleDate (int day, int month, int year ) { this.day = day ; this.month = month ; this.year = year ; } public String toString () { return (day + "/" + month + "/" + year ) ;} public String weekday() { ..... } public Date addDays(int n) { ..... } }
public int diff (Date d ) { ..... }
[Reasonably amusing exercise: Finish off this implementation!] Here the compiler will check that all the methods mentioned in the Date interface are in fact implemented in the class SimpleDate and have the correct types. If so, any object of class SimpleDate will fulfil the “contract” for a Date object. We can then write client code entirely in terms of the interface, using Date as a type, and without any reference to the class SimpleDate: Date dateDueBack (Book book) { if ( book.type == Journal) { return today.add (3) ; // journals can be borrowed for 3 days } else { return today.add (28) ; // other books can be borrowed for 28 days } } 5.4 Why use interfaces? In the above example, notice that all the information in the interface was also repeated in the class declaration. So in one sense, the interface is redundant. However, providing explicit interfaces for (at least) the most important classes in a program is considered to be good programming practice for several reasons: 1 1
My statement of these reasons is adapted from Cornelius’s book (Section 11.13). Cornelius also quotes OO expert Mark Fussell as saying: “Use interfaces as the glue throughout your code
3
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
An interface declaration provides a clearer statement of the contract between a class and its client code (for instance, it might serve to document the fact that certain methods provided by the class are never used by a particular client). Writing the client code in terms of an interface means that less work is involved if you want to switch to a different implementation of the class. If the client code is written in terms of an interface, it is not compilation dependent on the implementation of the class. That is, one can make changes to the class code and recompile it without having to recompile the client code. This can be a significant advantage for very large programs. To illustrate the second point with reference to our example: we might later decide that in order to save memory space, we would switch to an implementation where a date was represented not by three integers (day, month, year) but by a single integer (say, by the number of days since 1 Jan 2000). We could therefore create another implementation of dates like this. class CompactDate implements Date { private int dayId ; CompactDate (int day, int month, int year ) { ..... } public String toString () { ..... } ..... // and the other three methods } [You might enjoy finishing this one off as well. Notice that the constructor and the method toString will be harder to implement than before, while the methods weekday, add and diff will be much easier.] Now, because the type name SimpleDate didn’t appear anywhere in our client code (Date was used instead), we don’t need to make any changes to the client code (or even recompile it!) in order to use it in combination with our new implementation of dates. 5.5 Multiple and inherited interfaces If a class implements an interface, the interface doesn’t have to include all the methods (or even all the public ones) provided by a class — it merely gives a kind of “minimum requirement” that the class must fulfil. Indeed, it is common for a class to implement several different interfaces, each capturing a different aspect or “view” of the class, and each reflecting the needs of different clients. (Remember that a client here is simply any other bit of code that is making use of the class. Clearly, the same class could be used in many different parts of the program for different purposes.) For example, since there is a natural ordering on dates, we might decide to add a compareTo method to our implementation of dates: instead of classes The most important reason to do this is that interfaces focus on the client’s needs: interfaces define what functionality a client will receive from an Object without coupling the client to the Object’s implementation. This is one of the core concepts to OO.”
4
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
int compareTo (Object o) { ..... } We could then declare that our class also implemented the API interface called Comparable (which contains just the single method compareTo): class SimpleDate implements Date, Comparable { ..... // same stuff as before , plus compareTo method } This would mean that any client code written in terms of either Date or Comparable (such as many of the sorting operations in the API classes) could be run on any SimpleDate object. The fact that classes are able to implement several different interfaces gives us at least some of the power that we would get from having multiple inheritance in Java. Interfaces can also extend (i.e. inherit from) other interfaces. Just as for classes, we specify this using an extends clause in the interface declaration, and we have the obvious notions of superinterfaces and subinterfaces. (Even multiple inheritance is allowed for interfaces, unlike classes.) For example, in the above scenario, a slightly more sensible way to proceed might be to consider compareTo as part of the Date interface, which we could do like this: interface Date extends Comparable { ..... // same stuff as before } Any class that was declared to implement Date would then automatically implement the Comparable interface, so that even if we wrote just class SimpleDate implements Date { ..... } we would be able to run client code written in terms of Comparable on objects of class SimpleDate. 5.6 A few more subtle points You will notice that in our class SimpleDate, all the methods mentioned in the interface were declared to be public. This is because all methods specified in an interface are implicitly public, and a class will not match the interface (i.e. it won’t compile!) if these methods aren’t declared public when they are implemented. The main use for interfaces is to make explicit which methods are available for a client’s use. A few other things besides methods can appear in an interface declaration (e.g. static final fields with explicit initializers), but these are rather out-of-the-way features of the language and we won’t discuss them here. There is one significant problem with this whole idea of modularity via interfaces, which we haven’t yet mentioned. Methods can be specified in interfaces, but constructors can’t. For example, the constructor SimpleDate in our class SimpleDate wasn’t (and couldn’t be) mentioned in the Date interface. This makes some sense, because the names of methods will always be the same in any class 5
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
implementing the interface, whereas the names of constructors always have to be the same as their class names (e.g. SimpleDate and CompactDate in our examples). The problem with this is that client code will almost certainly want to call constructors as well as methods — otherwise how will we ever create any objects of the class? It seems that the only way a client can create an object is by directly calling the constructor declared in the class: e.g. Date today = new SimpleDate (7,3,2003) ; Unfortunately, this violates the principle that client code depends only on the interface, not on the class. If we decided to switch to the CompactDate implementation, we would have to go through the client code changing all occurrences of the constructor SimpleDate to CompactDate. So, though we have reduced the dependency of the client code on the class implementation by specifying the methods in the interface, we have not eliminated the dependency completely. There is a standard way around this problem, using what is known as the factory pattern. In Java terminology, the word pattern is used to mean a commonly used idiom or way of using the language that addresses a certain problem. The basic idea behind the factory pattern is to define a “factory class” which provides methods for manufacturing objects of one or more other classes. For instance, in addition to the Date interface and its various implementations, we might want to set up a “date factory” as follows: class DateFactory { public static Date makeDate (int day, int month, int year ) { return new SimpleDate(day,month,year) ; } } We can then write DateFactory.makeDate instead of new SimpleDate in our client code whenever we want to construct a new date object. What have we gained from this? The main advantage of using the factory pattern is that if we decide to switch from using the SimpleDate implementation to the CompactDate one, then rather than having to make changes throughout our client code, we only need to make the change in one place, namely within the body of DateFactory. In this way, the principle of independence of the client code from the class implementation can in effect be restored. John Longley 2003, Ian Stark 2002
6
CS2 Advanced Programming in Java note 5
CS2Bh 26.2.2004
Exercises The following are some questions to think about or perhaps discuss in your tutorials, relating to the whole of this thread so far; some are rather open-ended. 1. Suppose we wish to write an equals method for the class Person from APJ Lecture Note 2. Which do you think is a better “header” for such a method: boolean equals (Person p) ; or boolean equals (Object o) ; Why? Write out a reasonable implementation of this method. 2. Some other OO languages such as Eiffel require you indicate explicitly when a method declaration is overriding another method declaration. It could be argued that it would be better if Java made you do this, e.g. with something like void moveBy (int x, int y ) overrides { ..... } What advantages might this have? (I can think of two.) Can you think of any disadvantages it might have? 3. In our Date interface, there is something slightly weird about the declaration int diff ( Date d). Can you see what it is? (You will probably discover it if you try to implement this method.) Can you think of a better way to achieve what we really want? Do you think this points to a problem with Java? 4. Consider the use of the “date factory” class as outlined above. Does this give us compilation independence between the class implementation and the client code? (No. But why not?) Can this situation be improved, e.g. by providing an explicit interface for the date factory class itself? Or using any other method you can think of?
7