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.