Outline
Other Material
Like overloading, there are two distinct methods with the same name. But there are differences:
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.
Child classes need only override one method (for example <) to get effect of all relationals.
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) { ... } }
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.
There are a number of reasons to use replacement of methods.
We will give examples of the latter two.
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
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.
The parent method is always executed first.
If it executes the special statement
If a class has no child, then an inner statement has no effect.
This guarantees that whatever initialization the parent class
performs will always be included as part of the initialization of
the child class.
The type signatures are the same in both parent and child classes,
The type signature in the child class differs
"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.
Intro OOP, Chapter 16, Slide 12
Refinement in Beta
Beta is interesting in that it always uses refinement.
inner;
then, at that point the child method is executed. The child class can
in turn do an inner, and so on arbitrarily.
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.
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
but the method was not declared as virtual in the parent class.
Redefinition
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