home—info—lects—labs—exams—hws
tutor/PIs—breeze (snow day)
Object120 + its docs—java.lang docs—java.util docs
lect14c
3 uses of inheritance
Review inheritance
-
Inheritance:
If class B extends A, we say “B is-a A”.
Anything a A can do, a B can do too (and possibly more).
For example, because Animals can speak,
then automatically Dogs can speak.
Note that all fields and methods which are common to all subclasses
are actually put inside of the superclass
only once.
That is, the code for speak and the field sound
aren't repeated in each of Cat, Python, Dog,
Narwhale, etc..
Mathematically: the set of Bs is a subset of the set of As.
-
polymorphism:
If a method takes a parameter of type A,
you can actually pass it a B if you want.
For example,
static String threaten( Animal attacker, Animal defender ) {
return attacker.speak() + defender.speak() + attacker.speak();
}
|
You can pass in a Dogs and a Python, no problem,
since they each know how to speak.
You can even pass in subclasses of Animals which we'll invent tomorrow.
As long as it extends Animal, it's guaranteed to speak.
(Note that the method can't say attacker.wagTail(), because
that is a behavior which some Animals have, but not all.)
It's a straightforward concept, but it gets the fancy name “polymorphism”
because it's the first time that we have an object which is constructed as
one type (say, Python), yet declared as another (Animal).
We sometimes speak of the object's “underlying type” and its
“declared type”.
In fact, with polymorphism,
we can have — for the first time — the fact that
the “declared” type of a variable might differ from its
“actual” type:
Animal a = new Dog( "Fido" );
java.util.List<Dog> pound = new java.util.LinkedList<Dog>();
|
-
Overriding
If we declare a method in a subclass which had already been declared in a superclass,
we can refine its behavior.
This interacts with polymorphism.
This explains how toString works, and why code written at Sun Microsystem labs
can call our toString inside (say) Dog.
We saw this when we printed a LinkedList<Dog>.
(Don't confuse “override” — which is way cool —
with “overload”, which is convenient but not deep.)
Three Applications of Inheritance
Based on hw10—gRUe solution,
we give three ways to refactor it and make it more flexible,
all using inheritance.
-
Recall the
basic solution to hw10-grue (see WebCT),
as we went over in lab yesterday.
- Inheritance, to avoid repeating the same code in each subclass:
We had repeated code:
One obvious place is
Room#arrayToString(Room[])
and
Treasure#arrayToString(Treasure[]),
which are identical except for the parameter-type.
(This is due in part to the fact that they both happen to have a
method toShortString.)
We want one function which can be given an argument
either of type Room[],
or of type Treasure[].
This is reminiscent of how we had methods that
worked either for Dogs or Cats.
Let's take everything that Room and Treasure
have in common, and put it into its own class:
class Describable {
String name;
String description;
String toShortString() { return this.name; }
String toLongString() { return this.toShortName() + " -- " + this.description; }
String arrayToString( Describable[] ds ) {
// polymorphism: ds might be a Room[], or it might be a Treasure[];
// either way this function will work regardless.
…ds[…].toShortString()…
// Every Describable has a toShortString() method,
//so we know it's legal to call that.
}
class Room extends Describable { … }
class Treasure extends Describable { … }
|
You can see the
full code on WebCT,
which also shows how the constructors work:
having the Treasure constructor start by
calling the Describable constructor
(which is in charge of initializing
the fields common to all Describables);
that's what the keyword “super” does.
That was a canonical use of inheritance:
to factor out repeated code/fields.
Now let's look at a more sophisticated use of inheritance:
How to pass code around as data.
- The command pattern
We have a big if-else-if loop
driving Explorer#explore().
When we add a new command, we need to add another branch.
It works pretty well, but it kinda makes me which I just had
a big collection of string/code pairs.
Say, an array of "command" objects where each command
has both a name, and a the code/method to call corresponding to that command.
Some languages would allow that, but Java doesn't:
we can have a class with a field for the name (type String),
but what would the type for the field for the code/method?
Methods aren't values, in Java.
1
So we'll contort ourselves by using the “command pattern”:
A new class Command, and it has one method
“makeItSo”.
We won't have many objects with different code;
we'll have many subclasses each with their own, overridden version
of makeItSo:
That is, we'll have
class LookCommand extends Command, and
class HelpCommand extends Command, etc..
We can replace our big
if-else-if loop
with an array of Command objects;
the user types in a word and we just loop over our array of Commands;
when we find a Command whose name matches what
the user typed we can call its makeItSo method.
You can see the
full code on WebCT.
What dis/advantages does it have?
If we start to have dozens of Commands or more,
and/or each of those Command's code can be very long,
we can pull that complexity out of class Explorer into
its own class.
More importantly, we also gain other flexibility:
- The moment we say Commands have a name,
we are reminded of Describable.
If we make class Command extends Describable,
then we'll get all the Describable methods
working for Commands, for free!
-
Since the array of known Commands is data,
we can modify it as the program is running.
For example,
we might have hundreds of possible Commands but
any particular Explorer might only have access to some
of them (based on their rank, privileges, etc.).
-
If we get fancier (keeping the name/command coupling in a
java.util.Map),
it also lets us more easily customize/internationalize the names
of commands.
(In fact, if we worked to internationalize
class Describable,
then our Commands get
internationalized for free!)
There can be drawbacks, though:
As written,
when the user types a name that doesn't correspond to any Command,
we have still have our code invoke a makeItSo method;
we just use a default dontKnowHow.makeItSo(Explorer) —
which doesn't have access to whatever (erroneous) request the user
just typed.
So we are now unable to say something like
“I don't know how to loo”,
and we've retreated to just saying
“I don't know how to do that”.
To regain our former ability, we'd either have to provide extra
arguments to makeItSo,
and/or not have the “I don't know” code be
an object of type Command.
- The strategy pattern
Recall the note at the end of hw10,
about I/O, terminal windows, and perhaps using
javax.swing.JOptionPane#showInputDialog
and relatives instead.
It seems that we can either have our code refer to
System.in and System.out,
or
we can have our code refer to the javax.swing.JOptionPane
methods… but not both.
That is, not unless we use the Strategy pattern!
We will make a new class, whose job is to interact with the user.
We'll call this a
GrueIO.
A GrueIO will only have a three tasks (methods) —
- present a message to the user;
- prompt the user for an integer;
and
- prompt the user for a string.
Our Explorer won't have any mention of System.in
or javax.swing or anything;
it will simply have a field io (of type GrueIO),
and that object will communicate with the user.
The first approach would be to have class TextIO implements GrueIO;
this object's methods will just use System.out
and Scanners as usual.
But: we can also make a separate
class GuiIO implements GrueIO which uses those
javax.swing methods to communicate with the user.
Once we've done this, the beauty is:
we can change our method of IO on the fly!
In particular, we can make a command to do this:
Inside Explorer#explore()
(the original, pre-Command version),
we'll have:
…
else if (command.equals("gui")) { this.io = new GuiIO(); }
else if (command.equals("text")) { this.io = new TextIO(); }
else if …
|
Now, the user can freely switch back and forth between the two
input methods at will!
You can see the
full code on WebCT.
While each of the above three improvements can be done in different ways
(and sometimes w/o introducing new classes),
it's more cleanly done with inheritance and classes.
There is an adage in software architecture:
If your program is becoming too complicated,
add more classes.
You can see Wikipedia (or other books)
on other examples of these “software design patterns”,
or others.
(In particular, I find the delegate pattern quite useful in practice.)
Interface vs. abstract class (optional)
-
Like an abstract class, interfaces are useful only for extending;
you can't make an instance of the interface itself.
-
An interface is like a class except that it contains no code --
it only has method signatures
(and static final constants).
- By comparison, an (abstract) class can contain fields and actual code,
which gets shared.
- A class can only extend one superclass (in Java).
- A class can implement many interfaces.
(ii) and (iii) are advantages to abstract classes,
but (iv) and (v) are an advantage to interfaces.
We'll come back to this topic below, trying to get the best
of both worlds.
Example:
Suppose we want to sort a list
of PizzaServers by salary (ascending).
We write some complicated to do so (perhaps after taking ITEC 220);
it will involve statements like
List<PizzaServer> sort( List<PizzaServer> employees ) {
// ...code, perhaps including loops...
Employee itm1 = employees.get(…);
Employee itm2 = employees.get(…);
if (itm1.getSalary() < itm2.getSalary()) …
// ...
}
|
(where t1 and t2 are local variables,
perhaps inside a loop).
Then, later, we want to sort a list of Dogs, by age (ascending).
This is practically the same problem;
the only difference is that this version will involve statements like
if (itm1.getAge() < itm2.getAge()) …,
(where itm1 and itm2
are now Dogs
instead of PizzaServers).
Yech! … nearly-repeated-code which is impossible to
factor out because one deals with getWeight and another with
getAge.
the Comparable interface
Interfaces, to the rescue!
In particular, the
the java.lang.Comparable
interface.
If both Dogs and PizzaServerss implement
the interface, then our sorting code can include
if (itm1.compareTo(itm2) < 0) /* think: "itm1 < itm2" */ …
|
Then, all that remains is telling Java how to compare
two Dogs -- in our case, by increasing age:
class Dog implements Comparable<Dog> {
//... other Dog fields/methods
public int compareTo( Dog other ) {
if (this.getAge() < other.getAge()) {
return -1; // Any negative number is good enough to satisfy the interface.
}
else if (this.getAge() == other.getAge()) {
return 0;
}
else if (this.getAge() > other.getAge()) {
return +1; // Any positive number will suffice.
}
else {
System.err.println( "Shouldn't reach this far." );
return 0; // Better than returning a bad answer: Throw an exception.
}
}
}
|
(Challenge: Can you find a one-line way to implement this method?)
Similarly,
PizzaServers would have a similarly have a method
compareTo which captures the notion
“by increasing salary”.
In fact, we could sort songs alphabetically-by-artist:
we'd have the Song method compareTo
which returns a negative number if the artist's name comes
alphabetically before the artist of another song.
How can we tell if one String is before another,
alphabetically?
Fortunately,
class String implements Comparable itself!
Practice
Write compareTo for Songs,
so that it compares alphabetically by artist.
Practice
If
class String didn't implement
Comparable,
how would we figure out whether one String comes
before another alphabetically?
Hint: use a loop, and compare individual chars with
>2
Note that we don't want to have these two classes both
inherit from some common superclass, each overriding compareTo
appropriately.
You might argue that perhaps class Object should've had
including compareTo in the same way they already
included
toString
and
equals.
However, there will always be new behaviors which people might
want in the future,
so having interfaces gives us, as programmers,
a power different from abstract classes.
Exercise
How do we need to modify class PizzaServer,
so that it implements Comparable?
Once a class implements Comparable, we can sort a list of them:
java.util.List<PizzaServer> employees = new java.util.LinkedList<PizzaServer>();
// ... add to the list...
employees.toString();
// `sort` is a static method; only compiles if PizzaServer implements Comparable:
java.util.Collections.sort( employees );
employees.toString();
|
Another example of interfaces:
java.util.List is an interface,
which is implemented by LinkedList,
as well as a class named ArrayList.3
This means that if we change the signature
int numCoversOn( LinkedList<Song> otherSongs )
to
int numCoversOn( List<Song> otherSongs )
then our function will be able to handle both
LinkedLists, as well as ArrayLists,
as well as any other List variant people might invent
in the future!
1In some languages, code is a first-class value
which can be passed around, stored in fields, etc..
In othose languages, we wouldn't even bother calling this
a 'command pattern'; we'd just have a structure whose field was code.
↩
2You can also subtract one char
from another, and then compare the result (as an int)
to zero.
↩
3
(Its name is a bit confusing;
an ArrayList is not an array!
It's a List, since it can get(index)
and add
and answer size() and isEmpty().
That is, it implements the List interface,
so therefore an ArrayList is a List.
You can't use square-brackets or the field length with
ArrayList.
It derives its name from the fact that under the hood,
it uses arrays to help it organize its contents.
But we don't care about how it works, as long as it
meets its interface.
↩
home—info—lects—labs—exams—hws
tutor/PIs—breeze (snow day)
Object120 + its docs—java.lang docs—java.util docs