Introduction to Object Oriented Programming, 3rd Ed

Timothy A. Budd

Chapter 16

Overriding

Outline

  1. Roadmap
  2. Difference from Overloading
  3. Overriding Comparison in Smalltalk
    1. Subclasses of Magnitude in Little Smalltalk
    2. Overridden Relationals
  4. Notating Overriding
  5. Replacement and Refinement
    1. Reasons to use Replacement
      1. Overriding a Default Method
      2. Overriding for Optimization
        1. More Efficient Versions in class True
      3. Downside of Replacement
    2. Refinement in Beta
      1. Example, Print Anchors
      2. Making Child Classes
    3. Simulating Refinement with Replacement
    4. Constructors use Refinement
  6. Overridng versus Shadowing
    1. Shadowing of Instance Variables in Java
    2. Shadowing Methods
    3. Overriding, Shadowing and Redefinition
  7. Covariance and Contravariance
    1. Contravariant Return Types
    2. A Safe Variance Change
  8. Chapter Summary

Other Material

Intro OOP, Chapter 16, Outline

Roadmap

A method in a child class overrides a method in the parent class if it has the name name and type signature.
In this chapter we will investigate some of the issues that arise from the use of overriding.
Intro OOP, Chapter 16, Slide 01

Difference from Overloading

Like overloading, there are two distinct methods with the same name. But there are differences:
Intro OOP, Chapter 16, Slide 02

Overriding Comparisons in Smalltalk

An interesting example of overriding in found in class Magnitude in Smalltalk.
<= arg
	^ self < arg or: [ self = arg ]

>= arg
	^ arg <= self

< arg
	^ self <= arg and: [ self ~= arg ]

> arg
	^ arg < self
Notice how these definitions are circular.
Intro OOP, Chapter 16, Slide 03

Subclasses of Magnitude in Little Smalltalk

Intro OOP, Chapter 16, Slide 04

Overridden Relationals

Child classes need only override one method (for example <) to get effect of all relationals.

Intro OOP, Chapter 16, Slide 05

Notating Overriding

In some languages (Smalltalk, Java) overriding occurs automatically when a child class redefines a method with the same name and type signature.

In some languages (C++) overriding will only occur if the parent class has declared the method in some special way (example, keyword virtual).

In some languages (Object Pascal) overriding will only occur if the child class declares the method in some special way (example, keyword override).

In some languages (C#, Delphi) overriding will only occur if both the parent and the child class declare the method in some special way.

class Parent {   // C# example
   public virtual int example (int a) { ... }
}
class Child : Parent {
   public override int example (int a) { ... }
}
Intro OOP, Chapter 16, Slide 06

Replacement and Refinement

There are actually two different ways that overriding can be handled: Most languages use both types of semantics in different situations. Constructors, for example, almost always use refinement.
Intro OOP, Chapter 16, Slide 07

Reasons to use Replacement

There are a number of reasons to use replacement of methods. We will give examples of the latter two.
Intro OOP, Chapter 16, Slide 08

Overriding a Default Method

We saw one example of overriding a default method in the solitare game. Here is another from Smalltalk. Class Number has child classes Integer, Fraction and Float. Method in class Number is defined as follows:
"class Number"
	sqrt
		^ self asFloat sqrt
The method in class Float clearly must perform something different, in this case actually computing the square root. The parent class has a method that will work in most, but not all, child classes.
Intro OOP, Chapter 16, Slide 09

Overriding for Optimization

Here is an example where a child class can do the same action more efficiently than the parent class. Class Boolean has child classes True and False, and defines the following methods:
"class Boolean"

	& right
		self ifTrue: [ right ifTrue: [ ^ true ] ].
		^ false

	| right
		self ifTrue: [ ^ true ].
		right ifTrue: [ ^ true ].
		^ false
These very general algorithms will work for either true or false values.
Intro OOP, Chapter 16, Slide 10

More Efficient Versions in class True

In class True we know the left argument is true, and therefore can make more efficient algorithms.
" class True "

	& right
		^ right

	| right
		^ true
Similar code in class False. These are faster than the code in the parent class, but have the same effect.
Intro OOP, Chapter 16, Slide 11

Downside of Replacement

The down side of replacement semantics is that there is no guarantee that the child class will have any meaning at all similar to the parent class.

For example, a child class could redefine sqrt to compute the cube root of its argument.

This goes back to the difference between subclasses and subtypes.

A refinement makes this more difficult to do, since whatever the parent does is guaranteed to be part of the child. This is why most languages use refinement semantics for constructors.

Intro OOP, Chapter 16, Slide 12

Refinement in Beta

Beta is interesting in that it always uses refinement.

The parent method is always executed first. If it executes the special statement

	inner;
then, at that point the child method is executed. The child class can in turn do an inner, and so on arbitrarily.

If a class has no child, then an inner statement has no effect.

Intro OOP, Chapter 16, Slide 13

Example, Print Anchors

Here is an example, printing html anchor tags.
class Anchor {
	public void printAnchor () {
		print('<A href="http:');
		inner;
		print('">');
	}
}
If we create an instance and class printAnchor, the output we expect will be produced.
	Anchor a = new Anchor();
	a.printAnchor();

<A href="http:">
Intro OOP, Chapter 16, Slide 14

Note, I have translates these into a pseudo-code, rather than true Beta syntax, so as to bring out the important points and not spend a lot of time discussing minor syntactic issues.

Making Child Classes

We can create child classes to any level:
class OSUAnchor extends Anchor {
	public void printAnchor () {
		print('//www.cs.orst.edu/');
		inner;
	}
}

class BuddAnchor extends OSUAnchor {
	public void printAnchor () {
		print('~budd/');
		inner;
	}
}

	Anchor anAnchor = new BuddAnchor();
	anAchor.printAnchor();
<A href=http:"//www.cs.orst.edu/~budd/">
Trace carefully the flow of control, and see how it differs from replacement.
Intro OOP, Chapter 16, Slide 15

Simulating Refinement with Replacement

In most languages the most important features of a refinement can be simulated, even if the language uses replacement.
void Parent::example (int a) {
    cout << "in parent code\n";
}

void Child::example (int a) {
    Parent::example(12); // do parent code
    cout << "in child code\n"; // then child code
}
Note that this is not quite the same as Beta, as here the child wraps around the parent, while in Beta the parent wraps around the child.
Intro OOP, Chapter 16, Slide 16

Constructors use Refinement

In most languages that have constructors, a constructor will always use refinement.

This guarantees that whatever initialization the parent class performs will always be included as part of the initialization of the child class.

Intro OOP, Chapter 16, Slide 17

Overriding versus Shadowing

It is common in programming languages for one declaration of a variable to shadow a previous variable of the same name:
class Silly {
	private int x; // an instance variable named x

	public void example (int x) { // x shadows instance variable
		int a = x+1;
		while (a > 3) {
			int x = 1; // local variable shadows parameter
			a = a - x;
		}
	}
}
Shadowing can be resolved at compile time, does not require any run-time search.
Intro OOP, Chapter 16, Slide 18

Shadowing of Instance Variables in Java

Java allows instance variables to be redefined, and uses shadowing.
class Parent {
	public int x = 12;
}

class Child extend Parent {
	public int x = 42; // shadows variable from parent class
}

	Parent p = new Parent();
	System.out.println(p.x);
12
	Child c = new Child();
	System.out.println(c.x);
42
	p = c; // be careful here!
	System.out.println(p.x);
12
Intro OOP, Chapter 16, Slide 19

Shadowing Methods

Many of those languages that require the virtual keyword in the parent class will use shadowing if it is omitted:
class Parent {
public: 	// note, no virtual keyword here
	void example () { cout << "Parent" << endl; }
};

class Child : public Parent {
public:
	void example () { cout << "Child" << endl; }
};

	Parent * p = new Parent();
	p->example()
Parent
	Child * c = new Child();
	c->example()
Child
	p = c; // be careful here!
	p->example()
Parent
Intro OOP, Chapter 16, Slide 20

Overriding, Shadowing and Redefinition

Overriding The type signatures are the same in both parent and child classes,
and the method is declared as virtual in the parent class.
Shadowing

The type signatures are the same in both parent and child classes,
but the method was not declared as virtual in the parent class.

Redefinition

The type signature in the child class differs
from that given in the parent class.

Intro OOP, Chapter 16, Slide 21

Covariance and Contravariance

Frequently it seems like it would be nice if when a method is overridden we could change the argument types or return types. A change that moves down the inheritance hierarchy, making it more specific, is said to be covariant. A change that moves up the inheritance hierarchy is said to be contravariant.
class Parent {
	void test (covar : Mammal, contravar : Mammal) : boolean
}

class Child extends Parent {
	void test (covar : Cat, contravar : Animal) : boolean
}
While appealing, this idea runs into trouble with the principle of substitution.
	Parent aValue = new  Child();
	aValue.text(new Dog(), new Mammal()); // is this legal??
Intro OOP, Chapter 16, Slide 22

Contravariant Return Types

To see how a contravariant change can get you into trouble, consider changing the return types:
	class Parent {
		Mammal test ( ) {
			return new Cat();
		}
	}

	class  Child extends Parent {
		Animal test () {
			return new Bird();
		}
	}

	Parent aParent = new Child();
	Mammal result = aValue.test(); // is this legal?
Most languages subscribe to the novariance rule: no change in type signatures.
Intro OOP, Chapter 16, Slide 23

A Safe Variance Change

C++ allows the following type of change in signature:
class Parent {
public:
	Parent * clone () { return new Parent(); }
};

class Child : public Parent {
public:
	Child * clone () { return new Child(); }
};
No type errors can result from this change.
Intro OOP, Chapter 16, Slide 24

Chapter Summary

Intro OOP, Chapter 16, Slide 25