©2006 Felleisen, Proulx, et. al.
Goals: - Abstracting over Data Types.
Over the past several weeks we have defined a number of lists: lists of books, persons, recordings, blocks, and more - repeating the same code again and again. The Design Recipe for Abstraction will guide us in learning to remove the repetitions code. In the process, we need to learn a couple of new language features that support such abstractions.
Let us compare the code for defining a list of books and a list of authors:
+---------+ | ILoBook |<------------+ +---------+ | / \ | | | - - - - - - - - - - | | | | +----------+ +--------------+ | | MTLoBook | | ConsLoBook | | +----------+ +--------------+ | +----------+ +-| Book first | | | | ILoBook rest |--+ | +--------------+ | v +---------------+ | Book | +---------------+ | String title | | int year | +---------------+
+-----------+ | ILoAuthor |<--------------+ +-----------+ | / \ | | | - - - - - - - - - - | | | | +------------+ +----------------+ | | MTLoAuthor | | ConsLoAuthor | | +------------+ +----------------+ | +------------+ +-| Author first | | | | ILoAuthor rest |--+ | +----------------+ | v +--------------+ | Author | +--------------+ | String name | | int year | +--------------+
We also include the code for two methods we have seen several times:
size()
that counts the number of elements in the list, and
contains(Book/Author that)
that determines whether the list
contains the given book or the given author, respectively.
interface ILoBook{ // to compute the size of this list int size(); // is the given book in this list? boolean contains (Book that); } class MTLoBook implements ILoBook{ MTLoBook() {} int size(){ return 0; } boolean contains (Book that){ return false; } } class ConsLoBook implements ILoBook{ Book first; ILoBook rest; ConsLoBook(Book first, ILoBook rest){ this.first = first; this.rest = rest; } /* TEMPLATE: ..this.first.. - Book ..this.rest.. - ILoBook ..this.rest.size().. - int ..this.rest.contains(Book).. - boolean */ int size(){ return 1 + this.rest.size(); } boolean contains (Book that){ return this.first.same(that) || this.rest.contains(that); } } interface ILoAuthor{ // to compute the size of this list int size(); // is the given author in this list? boolean contains (Author that); } class MTLoAuthor implements ILoAuthor{ MTLoAuthor() {} int size(){ return 0; } boolean contains (Author that){ return false; } } class ConsLoAuthor implements ILoAuthor{ Author first; ILoAuthor rest; ConsLoAuthor(Author first, ILoAuthor rest){ this.first = first; this.rest = rest; } /* TEMPLATE: ..this.first.. - Author ..this.rest.. - ILoAuthor ..this.rest.size().. - int ..this.rest.contains(Author).. - boolean */ int size(){ return 1 + this.rest.size(); } boolean contains (Author that){ return this.first.same(that) || this.rest.contains(that); } }> class Examples{ Examples () {} Book b1 = new Book("DVC", 2003); Book b2 = new Book("LPP", 1942); Book b3 = new Book("HtDP", 2001); ILoBook mtbooks = new MTLoBook(); ILoBook booklist = new ConsLoBook(b1, new ConsLoBook(b2, mtbooks)); boolean testSize1 = mtbooks.size() == 0; boolean testSize2 = booklist.size() == 2; boolean testContains1 = this.mtbooks.contains(this.b1) == false; boolean testContains2 = this.booklist.contains(this.b2) == true; boolean testContains3 = this.booklist.contains(b3) == false; }> class Examples{ Examples () {} Author a1 = new Author("DB", 1956); Author a2 = new Author("StEx", 1900); Author a3 = new Author("MF", 1972); ILoAuthor mtauthors = new MTLoAuthor(); ILoAuthor authorlist = new ConsLoAuthor(a1, new ConsLoAuthor(a2, mtauthors)); boolean testSize1 = mtauthors.size() == 0; boolean testSize2 = authorlist.size() == 2; boolean testContains1 = this.mtauthors.contains(this.a1) == false; boolean testContains2 = this.authorlist.contains(this.a2) == true; boolean testContains3 = this.authorlist.contains(a3) == false; }
Figure 1: Class definitions for lists of books and authors
The class diagrams are nearly identical. So is the code. The only difference is the reference to the class of data that the elements of the list represent, either
Book
orAuthor
. In Java, every class implicitly extends the super class of all classes,class Object
. If we replace all occurrences ofBook
orAuthor
byObject
-- except for the definitions of the two classes, we get the follwing class diagram:
+-----------+ | ILoObject |<------------+ +-----------+ | +-----------+ | / \ | --- | | | ------------------- | | | | +------------+ +----------------+ | | MTLoObject | | ConsLoObject | | +------------+ +----------------+ | +------------+ +-| Object first | | | | ILoObject rest |-+ | +----------------+ | v +------------+ | Object | +------------+ | Type ??? | | ... | +------------+
Figure 2: A class diagram lists of books and authors
The type of the field
first
in the classConsLoObject
isObject
. That means, that it can be an instance of any of the classes that extendObject
. Obviously, instances ofBook
orAuthor
are among them. We can test our examples of data -- and see that the examples of lists of books and of the lists of authors are the same as before. We now compar the methods. The methodsize()
does not refer at all to the any instances of objects contained in the list - indeed, the code was already identical in the two original lists. We now look at the similarities and differences between the two implementations of the methodcontains
.
class ConsLoBook implements ILoBook{ /* TEMPLATE: ..this.first.. - Book ..this.rest.. - ILoBook ..this.rest.size().. - int ..this.rest.contains(Book).. - boolean */ boolean contains (Book that){ return this.first.same(that) || this.rest.contains(that); } }
class ConsLoAuthor implements ILoAuthor{ /* TEMPLATE: ..this.first.. - Author ..this.rest.. - ILoAuthor ..this.rest.size().. - int ..this.rest.contains(Author).. - boolean */ boolean contains (Author that){ return this.first.same(that) || this.rest.contains(that); } }
Figure 3: The method contains for lists of books and authors
The two methods differ only in type of argument consumed by the method
contains
. If we replace the type byObject
, the super type of bothBook
andAuthor
everything is almost OK. The only problem is that the classObject
does not provide the methodsame
. Not only that. The method argument in the two cases is again different.We need a method
boolean same(Object obj)that is defined for any object we wish to look for in the list. We can do this for the classes
Book
andAuthor
by having both classes implement a common interfaceISame
:
interface ISame{ // is this the same as the given object? boolean same(Object obj); }We will first look at how this knowledge is leveraged inside of the definition of the method
contains
, then return to completing the definitions of the classesBook
andAuthor
.We first have to indicate inside the method
contains
that the fieldthis.first
is an instance of a class that defines the methodboolean same(Object obj)
. To do so, we use cast:
boolean contains (Object that){ return ((ISame)this.first).same(that) || this.rest.contains(that); }We enclose in parentheses the type we wish to cast to, and enclose in another set of parentheses the cast directive and the expression that should be cast to that type:
((ISame)this.first)
. The entire expression now represents a value of the typeISame
and can invoke any methods declared in theISame
interface. The compiler will replace then at run time look for a method with the signatureboolean same(Object obj)
. If we construct the list with an object that does not implement theISame
interface, the program will fail at the runtime with theClassCastException
.We now look at how the method
boolean same(Object obj)
is implemented in the classesBook
andAuthor
:
class Book implements ISame{ String title; int year; Book(String title, int year){ this.title = title; this.year = year; } boolean same(Object obj){ if (obj instanceof Book) return this.same((Book)obj); else return false; } boolean same(Book that){ return this.title.equals(that.title) && this.year == that.year; } }
class Author implements ISame{ String name; int year; Author(String name, int year){ this.name = name; this.year = year; } boolean same(Object obj){ if (obj instanceof Author) return this.same((Author)obj); else return false; } boolean same(Author that){ return this.name.equals(that.name) && this.year == that.year; } }
We have two different methods named
same
overloaded with two different types of arguments. Inside the methodcontains
the expected method for comparison only knows that the argument is of the typeObject
. As the result, the compiler invokes the methodsame(Object obj)
. Each of the two classes,Book
/Author
then first verifies that the argument is indeed anotherBook
/Author
, and if true, it invokes the originalsame
method that compares the corresponding fields.The tests are the same as before:
class Examples{ Examples () {} Book b1 = new Book("DVC", 2003); Book b2 = new Book("LPP", 1942); Book b3 = new Book("HtDP", 2001); ILoObject mtbooks = new MTLoObject(); ILoObject booklist = new ConsLoObject(b1, new ConsLoObject(b2, mtbooks)); boolean testSizeBook1 = mtbooks.size() == 0; boolean testSizeBook2 = booklist.size() == 2; boolean testContainsBook1 = this.mtbooks.contains(this.b1) == false; boolean testContainsBook2 = this.booklist.contains(this.b2) == true; boolean testContainsBook3 = this.booklist.contains(b3) == false;
> Author a1 = new Author("DB", 1956); Author a2 = new Author("StEx", 1900); Author a3 = new Author("MF", 1972); ILoObject mtauthors = new MTLoObject(); ILoObject authorlist = new ConsLoObject(a1, new ConsLoObject(a2, mtauthors)); boolean testSizeAuthor1 = mtauthors.size() == 0; boolean testSizeAuthor2 = authorlist.size() == 2; boolean testContainsAuthor1 = this.mtauthors.contains(this.a1) == false; boolean testContainsAuthor2 = this.authorlist.contains(this.a2) == true; boolean testContainsAuthor3 = this.authorlist.contains(a3) == false;
The class diagram for our complete solution is shown in figure 4: We summarize what we have learned:
We replace the list of
Book
,Author
, or some other class by a list ofObject
. We refer to the classBook
, orAuthor
, etc. as the target classes and to the classes that implement the list structure aslist
classes.
interface ILoObject class MTLoObject implements ILoObject class ConsLoObject implements ILoObject
If a method in the
list
class hierarchy requires an argument of thetarget
type, we replace if with the argument of the typeObject
.
interface ILoObject{ boolean contains(Object obj); }
If the method in the
list
classes invokes a method that is to be implemented by ourtarget
class, we proceed as follows:
class CosnLoObject ... { boolean contains(Object obj){ ... this.first.same(that) ... } }
Define an common interface (we will call it
Icommon
) that contains only the desired method. If the method consumes an object of the type of thetarget
class, change it to the typeObject
.
interface ISame { boolean same(Object obj); }
Make every
target
class implement theIcommon
interface. If the method consumes an object of theObject
class that previously was of the typetarget
class, the method will have two clauses:
If the argument is
instanceof
thetarget
class, invoke the original method, casting the argument to thetarget
class. The method is now overloaded -- with two variants of the argument type - the original one of thetarget
type and the typeObject
.
Else, the method invocation is invalid and produces an error. You decide whether it should just be
false
for methods that produce boolean value, or whether the method shouldthrow
an exception (probably theClassCastException
.
class Book implements ISame{ ... boolean same(Object obj){ if (obj instanceof Book) return this.same((Book)obj); else throw new ClasCastException( "Cannot compare a Book with other object"); } ... }
In the
list
classes cast thetarget
object to the type of theIcommon
interface.
+-----------+ | ILoObject |<------------+ +-----------+ | +-----------+ | / \ | --- | | | ------------------- | | | | +------------+ +----------------+ | | MTLoObject | | ConsLoObject | | +------------+ +----------------+ | +------------+ +-| Object first | | | | ILoObject rest |-+ | +----------------+ | | +--------------------------+ | | interface ISame | | +--------------------------+ | | boolean same(Object obj) | | +--------------------------+ | / \ / \ | | | | - - - - - - | v | +------------+ | | Object | +------------+ | | Type ??? | | ... | | +------------+ / \ | --- | | | +-----------------------+ | - - -|- - - - - - - - - - - -|- - - - | | | | +--------------+ +-------------+ | Book | | Author | +--------------+ +-------------+ | String title | | String name | | int year | | int year | +--------------+ +-------------+
Figure 4: A class diagram for a list of objects
Last modified: Sunday, March 12th, 2006 4:39:55pmHTML conversion by TeX2page 20050501