Outline
Other Material
<= arg ^ self < arg or: [ self = arg ] >= arg ^ arg <= self < arg ^ self <= arg and: [ self ~= arg ] > arg ^ arg < selfNotice how these definitions are circular.
Child classes need only override one method (for example <) to get effect of all relationals.
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) { ... } }
"class Number" sqrt ^ self asFloat sqrtThe 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.
"class Boolean" & right self ifTrue: [ right ifTrue: [ ^ true ] ]. ^ false | right self ifTrue: [ ^ true ]. right ifTrue: [ ^ true ]. ^ falseThese very general algorithms will work for either true or false values.
" class True " & right ^ right | right ^ trueSimilar code in class False. These are faster than the code in the parent class, but have the same effect.
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
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.
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:">
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.
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.
This guarantees that whatever initialization the parent class performs will always be included as part of the initialization of the child class.
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.
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
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
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,
|
Redefinition |
The type signature in the child class differs
|
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??
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.
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.