When to Use Inheritance

Is-a vs. has-a

For a class to be a subclass of another class, their functionality should be related.

The is-a rule: A class Y should be a subclass of a class X only when every Y is an X.

Y should not be a subclass of X if every Y has an X.

But what does it mean to say a Y is an X?

• It might mean that the behavior and data associated with the child class Y are always an extension of the parent class X.

A subclass must have all the properties of the parent class, and additional properties as well.

• It might mean that Y is a more specialized (and therefore restricted) form of X.

In this way, it is a contraction of the parent type.

This tension between inheritance as expansion and as contraction causes confusion in how to employ it.

Four perspectives on inheritance

[Skrien §3.1] Programmers use inheritance for several reasons.

• Code reuse. It is better to inherit existing code than to duplicate it.

• Is-a relationship. This is the “purest” reason for using inheritance, since it allows subclassing for specialization.

• Public interface. If class S responds to all the messages that class C responds to, and then some, it seems appropriate for S to be a subclass of C.

• Polymorphism. It seems good for class S to be a subclass of class C because it would be convenient to assign objects of class S to variables of type C.

But when are these good reasons for using inheritance?

Code reuse

[Skrien §3.2] Many times, classes have similar protocols, with some operations in common. Is this a sufficient reason for making the class with more operations a subclass of the other class?

In general, a good way around the dilemma is to

• create a third class that has the common functionality, and

• make sure that both of the classes have-an attribute that is an element of the third class.

What example of this is given in the text?

In this case, instead of invoking a method of the superclass, we send a message to an instance of the third class. This is called delegation.

Is-a relationship

[Skrien §3.3] Consider the case of a rectangle and a square. A square is-a rectangle, and the two could easily share code.

So if we are implementing a graphics library, should Square be a subclass of Rectangle?

public class Rectangle

{

private int x, y, width, height;

public Rectangle(int x, int y, int w, int h) {

this.x = x; this.y = y; width = w; height = h;

}

public int getWidth() { return width; }

public int getHeight() { return height; }

public int getArea() { return width * height; }

public int getPerimeter()
{ return 2* (width + height); }

public setTopLeft(int newx, int newy)
{ x = newx; y = newy; }

public erase(Graphics g) { ... }

public draw(Graphics g) { ... }

public void setSize(int w, int h)
{ width = w; height = h; }

}

public class Square extends Rectangle

{

public Square(int x, int y, int side) {

super(x, y, side, side);

}

}

What’s wrong with this?

Can we “nullify” that method?

Principle of least astonishment: In designing a language or computer program, generally, you should ensure the effect of any command is the one that least astonishes the user.

Why is it good to follow this principle?

In the last lecture, we saw a WhiteOnBlackLabel, which redefined some behavior of JLabel. Did this violate the PLA?

But this definition doesn’t say anything about classes. How can we apply it to inheritance?

Liskov substitution principle: Class S should be made a subclass of class C only if, for every method in C’s and S’s interface, S’s method

• accepts as input all the values that C’s method accepts (and possibly more) and

• does everything with those values that C’s method does (and possibly more).

In the case of squares and rectangles, the Square method setSize( … )can’t do everything that Rectangle’s
setSize( … ) method does.

Public interfaces

[Skrien §3.4] Let’s consider two classes that have three of these perspectives (code reuse, is-a, and similar public interfaces). Does this mean that one should be a subclass of the other?

A Student has all the properties of a Person, as well as some additional properties (e.g., major, GPA). So, should Student inherit from Person?

Well, a Student can become an Employee. Employee also has behavior in common with Person. What is the problem if a Student needs to become an Employee?
/

How can the effect be achieved more elegantly?

public class Student

{

private Person me;

private AcademicRecord myRecord;

public String getAddress()
{ return me.getAddress(); }

public float getGPA() {

...compute it from the academic record...

}

...other methods and data...

}

Guideline: If B is a role played by A, then B is-not-an A, but rather, B has-an A.

How are these has-a relationships shown in UML?

An association is a relationship between classes.

• A plain line indicates two-way navigability.
• A line with an arrow indicates one-way navigability.
• Composition, or ownership, is indicated by a blackened diamond at the owner’s end. /

• Numbers at ends of the line indicate multiplicities; e.g., a person has two arms.

Exercise: Give an example of a 1-to-n relationship.

Polymorphism

[Skrien §3.6] In the case above, we never really needed to treat a Student as a Person. One case in which we do is a GUI component.

For example, when we draw a window, we need to draw all the components in that window.

Let’s create a new component, a 2D slider (here’s where you can see a slider

http://java.sun.com/docs/books/tutorial/uiswing/components/slider.html).

Should 2DSlider be a subclass of Component?

Costs of Using Inheritance

[Skrien §3.7] The yo-yo effect. Given the title, can you describe what this is talking about?

Tight coupling of classes and subclasses. Due to information hiding, the author of a class can be sure that client classes depend only on the public interface of a class, not on its implementation.

Unfortunately, the same is not true of subclasses.

The example given in the text is a specialized class of Rectangle that keeps track of how many times its width is changed.

The superclass contains these methods:

public void setWidth(int newWidth)
{ width = newWidth; }

public void setHeight(int newHeight)
{ height = newHeight; }

public void setSize(int w, int h)
{ setWidth(w); setHeight(h); }

The subclass overrides setWidth:

public void setWidth(int newWidth) {

if( newWidth != width ) {

widthChangeCounter ++;

width = newWidth;

}

If the implementation of setSize is changed, the subclass’s setWidth may not work anymore. Why not?

Exercise: Unfortunately, this example seems somewhat contrived. Can you think of a better one?

Two examples

Drawing polygons

[Skrien §3.8] A drawing program that allows the user to draw polygons (rectangles, squares, triangles, etc.).

The drawing program displays a window with two parts:

• a large drawing canvas and

• a toolbar across the top of the window with tool buttons, one for each polygon.

The user clicks on a tool button to select a polygon and then clicks in the drawing canvas.
A copy of the selected polygon appears in the canvas centered where the user clicks.
The canvas contains a collection of Polygons. How should they be represented? /

Well, the various kinds of polygons could be subclasses of Polygon.

To display the polygons, we simply iterate through the list:

public void paint(Graphics g) {

for( int i = 0; polygons.size(); i++ ) {

Polygon poly = (Polygon) polygons.get(i);

poly.draw(g);

}

}

But should the different kinds of polygons be represented as subclasses of Polygon?

Sorting

Goal: Show how inheritance + interfaces = elegant code.

Let’s start with this code:

public class Sorter

{

public static void sort(String[] A)

{ ...code for sorting String arrays... }

public static void sort(Integer[] A)

{ ...code for sorting Integer arrays... }

...methods for sorting other kinds of arrays...

}

What is it supposed to do?

But, how many sort methods should Sorter implement?

Also, won’t it be inelegant to duplicate most of the code for sorting in each method?

Or if we want to sort in reverse order, or sort strings by length?

Before seeing how we can overcome these shortcomings, let’s take a look at the code for sorting.

public static void sort(Integer[] data)

{

for (int i = data.length-1; i >=1; i--) {

// in each iteration through the loop

// swap the largest value in data[0..i] into pos’n i

//find the index of the largest value in data[0.. i]

int indexOfMax = 0;

for (int j = 1; j <= i; j++) {

if (data[j] data[indexOfMax])

indexOfMax = j;

}

// swap the largest value into position i

Integer temp = data[i];

data[i] = data[indexOfMax];

data[indexOfMax] = temp;

}

}

public static void sort(String[] data)

{

for (int i = data.length-1; i >=1; i--) {

// in each iteration through the loop

// swap the largest value in data[0..i] into pos’n i

//find the index of the largest value in data[0.. i]

int indexOfMax = 0;

for (int j = 1; j <= i; j++) {

if (data[j].compareTo(data[indexOfMax]) > 0)

indexOfMax = j;

}

// swap the largest value into position i

String temp = data[i];

data[i] = data[indexOfMax];

data[indexOfMax] = temp;

}

}

How can we factor out the differences in these two routines so we can use a single sort routine?

public class Comparator

{

public int compare(String o1, String o2)

{

return s1.compareTo(s2);

}

public int compare(Integer o1, Integer o2)

{

int i1 = o1.intValue();

int i2 = o2.intValue();

return i1 – i2;

}

...compare(…) methods for other types of data

}

Let us decide that compare methods will return …

·  a negative integer if o1 is “less than” o2.

·  0 if o1 is “equals” o2.

·  a positive integer if o1 is “greater than” o2.

Now we can sort arbitrary objects with the same sort method:

public static void sort(Object[] data, Comparator comp)

{

for (int i = data.length-1; i >=1; i--) {

// in each iteration through the loop

// swap the largest value in data[0..i] into pos’n i

//find the index of the largest value in data[0.. i]

int indexOfMax = 0;

for (int j = 1; j <= i; j++) {

if (comp.compare(data[j], data[indexOfMax]) > 0)

indexOfMax = j;

}

// swap the largest value into position i

Object temp = data[i];

data[i] = data[indexOfMax];

data[indexOfMax] = temp;

}

}

The problem is that the compiler can’t determine which compare method to call. Why?

So, let’s give Comparator a compare method that takes two Objects as parameters:

public class Comparator

{

public int compare(Object o1, Object o2)

{

if( o1 instanceof String & o2 instanceof String )

return ((String) o1).compareTo((String) o2);

else if
(o1 instanceof Integer & o2 instanceof Integer) {

int i1 = ((Integer) o1).intValue();

int i2 = ((Integer) o2).intValue();

return i1 – i2

}

else

...deal with all the other types of data...

}

}

What’s wrong with this?

Any idea what we can do?

public interface Comparator

{

public int compare(Object o1, Object o2);

public boolean equals(Object o); // can be inherited

// from Object if not implemented in subclass

}

We can implement the interface in any class that needs to implement compare(…).

public class StringComparator implements Comparator

{

public int compare(Object o1, Object o2) {

return s1.compareTo( );

}

}

public class IntegerComparator implements Comparator

{

public int compare(Object o1, Object o2) {

return (Integer) o1 ;

}

}

Exercise: Finish the code in the above examples.

Note that the Comparator parameter of sort(…) can take any subtype of Comparator.

Once we pass in a comparator, it will be called and the objects to be compared will both be cast to the same type.

Does this solve the problem of trying to compare different kinds of objects?

Here's how a client who wants to sort an array of Strings alphabetically might use our sort method.

String[] data = ...initialize the data array...

comp = new ;

Sorter.sort(data, comp);

Here is a UML class diagram showing these relationships.

To sort an array of Strings ignoring the upper or lower case of the letters, the user can do exactly the same thing except define a different class implementing Comparator as follows:

public class StringIgnoreCaseComparator {

public int compare(Object o1, Object o2) {

String s1 = (String) o1;

String s2 = (String) o2;

return s1.compareToIgnoreCase(s2);

}

}

The text carries this example through in more detail, giving the final code for all of the classes. Note that Comparator is defined in java.util.Comparator.

Inheritance vs. delegation

[Skrien §3.12] Delegation can often achieve the same effect as inheritance. Let’s look at another example.

Consider the java.util.Stack class. How many operations does it have?

Exercise: Answer the questions below.

Suppose in a program you want a “pure” stack class—one that can only be manipulated via push(…) and pop().

Why would you want such a class, when Java already gives you that and more?

What is the “simplest” way to get a pure Stack class?

Or you could create Stack class “from scratch.” What’s wrong with doing this?

Another option is to create your own Stack class, but have it include a java.util.Stack.

What is the name for the approach are we using here?

Here’s what this class might look like.

public class MyStack

{

private java.util.Stack stack;

public MyStack(){stack = new java.util.Stack();}

public void push(Object o) { stack.push(o); }

public Object pop() { return stack.pop(); }

public object peek() { return stack.peek(); }

public boolean isEmpty(){return stack.empty();}

}

Let’s consider inheritance and delegation, from these perspectives.

·  Polymorphism.

·  Interface.

·  Efficiency.

·  Amount of code.

Lecture 17 Object-Oriented Languages and Systems XXX