Abstracting over the datatype

Code for this part of the lecture can be found here.

Over the past several weeks we have designed many list of different objects. By now it is clear that there is a great deal of repetition in the class definitions that represent lists. We wonder, whether this is an opportunity for abstraction. Furthermore, now that we no longer have the ProfessorJ support for testing, we need to find a way how to compare two lists, so that we can again write automated tests.

Looking back at the class definitions for lists we see that the only difference is in the type of object that is the first field of the Cons class, the class that represents the nonempty list.

The Design Recipe for Abstractions tells us to look for the only place where the two parts of the code are different and replace it with a common parameter. The common parameter in our case would be the type of object that the first field represents. We could use the type Object that is the super class of all classes in Java. However, in order to compare two lists for equality, we need to compare the two first items, then the next two items, and so on, until either we find a difference, or one of the two lists being compared ends before the other ends. We have used the same method for such comparison.

We can design an interface that represents this one method:

 interface ISame{
   // is this book the same as the given ISame object?
   public boolean same(ISame that);
 }

Now every class that implements the same method can be declared to implement the ISame interface, and we can then design the classes that represent a list of objects of the type ISame:

Consider the following class diagram:

             +---------------------------+                
             |  AList                    |                
             +---------------------------+                
	     | boolean same(Obj that)    |
	     | boolean contains(Obj that)| 
             +---------------------------+                
                         |                    
                        / \                   
                        ---                   
                         |                    
           ---------------------------          
           |                         |          
    +-------------+          +---------------+  
    | MTList      |          | ConsList      |  
    +-------------+          +---------------+  
    +-------------+     +----| ISame fist    | 
                        |    | AList rest    |   
                        |    +---------------+ 
			v
            +-------------------------+ 
            | ISame                   |   
	    +-------------------------+
	    | boolean same(ISame that)|
	    +-------------------------+
                        |
                       / \
                       ---
                        |
               --------------------
               |                  |
	+--------------+    +--------------+
	| Book         |    | Song         |
        +--------------+    +--------------+
        | String title |    | String title | 
	| String author|    | int    time  |
	| int    year  |    +--------------+  
        +--------------+    
           
            
    

We need to design the method same that returns true if this ISame object is the same as that given ISame object. Otherwise it returns false.

In class Book we have need to first make sure that the given object is an instance of the class Book. Once we know that, we can compare the two instances in the usual way, field by field. (We only follow loosely the design recipe --- mainly because you have already seen nearly all steps in the design of the same method when we were comparing shapes.)

To answer the first question we use the instanceof operator. An operator is used as a connector in a binary expression, e.g. 2 + 3 or 2 > 3 true && false. So, if b1 is an instance of the class Book and the class Book implements ISame, and s is an instance of the class Song, then the following expressions are valid and produce the shown values:

 b instanceof Book    ---> true
 b instanceof ISame   ---> true
 s instanceof Song    ---> true

We can now design the rest of the method, using casts to inform the compiler that we already know that the given object is an instance of the class .

  //is this book the same as that ISame object   
  boolean same(ISame that){
    if (that instanceof Book)
      return this.title.equals(((Book)that).title)
          && this.author.equals(((Book)that).author
          && this.year==((Book)that).year;
    else 
      return false; 
  } 
    

In class Song we implement the same method as follows:

  //is this song the same as that object   
  boolean same(ISame that){ 
    if (that instanceof Song)
      return this.title.equals(((Song)that).title)
          && this.time==((Song)that).time;
    else 
      return false; 
  } 
    

We can now design the same method for the class AList and its derived classes. If AList is an interface (and not an abstract or concrete class) we write the following in the header of the interface definition:

  //an interface to represent a list of objects that can be compared
  // using 'same' method
  interface AList extends ISame{

    //is this song the same as that object   
    boolean same(ISame that); 
  } 
    

So, a class implements an interface, while an interface extends another interface.

In class MTList the method just needs to verify that the given object is an instance of the MTList:

  //is this empty list the same as that object   
  boolean same(Object that){ 
    return (that instanceof MTList);
  } 
    

In class ConList the method follows the familiar pattern:

  //is this non-emty list the same as that object   
  boolean same(Object that){ 
    if (that instanceof ConsList)
      return this.first.same(((ConsList)that).first)
          && this.rest.same(((ConsList)that).rest);
    else 
      return false; 
  } 
    

Make sure you add examples of the use of these classes and methods.