Subclassing

advertisement
1
Subclassing
Object Oriented Programming
236703
Spring 2007
2
A Tale of Two Objects
// Object 1: "abc"
"abc".length == 3
"abc".charAt(1) == 'b'
"abc".charAt(2) == 'c'
// Object 2: "def"
"def".length == 3
"def".charAt(1) == 'e'
"def".charAt(2) == 'f'
Q: How are Object 1 and Object 2 different?
A (Hypothetical) Tale of Two
Objects
// Object 3: "uvw"
"uvw".length == 3
"uvw".charAt(1) == 'v'
"uvw".charAt(2) == 'w'
// Object 4: "xyz"
"xyz".length == 3
"xyz".charAt(1) == 'x' // ?!
"xyz".charAt(2) == 'y'
Q: How are Object 3 and Object 4 different?
3
4
Differences Between Objects
●
●
Recall: A class determines the behavior &
structure of its instances
(For a given class C), if we want:
–
A new object with a different state:
●
●
–
Just create a new instance of C, and set its fields as needed
(A class is an abstraction over sate)
A new object with a different behavior/structure
●
Introduce D
–
●
A new class that provides the requested behavior/structure
Create an instance of D
Behavioral Differences
"uvw".length() == 3
"uvw".charAt(1) == 'v'
"xyz".length() == 3
"xyz".charAt(1) == 'x'
●
●
xyz's should provide a different behavior for the
charAt() message
The solution: let's define two String classes
5
Deja-vu
class StrZero {
char[] cs;
public StrZero(char... arr) { cs = arr; }
public int length() { return cs.length; }
public char charAt(int i) { return cs[
i]; }
}
class StrOne {
char[] cs;
public StrOne(char... arr) { cs = arr; }
public int length() { return cs.length; }
public char charAt(int i) { return cs[
i-1]; }
}
●
Code duplication: Body of StrOne is almost identical to that
of StrZero
6
Subclassing: Avoid the repetition
class StrZero {
private char[] cs;
public StrZero(char... arr) { cs = arr; }
public int length() { return cs.length; }
public char charAt(int i) { return cs[i]; }
}
class StrOne extends StrZero {
public StrOne(char... arr) { super(arr); }
public char charAt(int i) { return cs[i-1]; }
}
●
●
Subclassing: New class specifies the delta from an existing class
– Structure + Protocol + Behavior of superclass is copied onto the
body of the subclass
– In most languages Type is also copied (see next slide)
– Forge is copied but is accessible only from constructors
– AKA: Copying principle
Result: less code duplication (thanks to the copying principle)
7
8
Subclassing vs. Subtyping
●
●
●
●
●
“A subclasses B”: Structure+Protocol+Behavior of B is
copied into A
“A subtypes B”: An object of type A is assignable into a
variable of type B
In Java:
“A extends B” = “A subclasses B” + “A subtypes B”
This equation is true in most languages
Sometimes we only want a subclass, not a subtype
– (We'll see some examples later...)
StrOne Subtypes StrZero
●
(Remember: subtyping means assignability)
●
An StrOne object is assignable to an StrZero variable:
StrZero s = new StrOne('a', 'b');
●
●
Polymorphism: The ability to take more than one form
– Main benefit: Reduces deja-vu
The sizeIs method is polymorphic:
boolean sizeIs(StrZero s, int n) {
return s.size() == n;
}
– sizeIs() can work with either StrZero
● Or any other subtype of StrZero
–
–
or StrOne objects
This is an example of Subtyping-based polymorphism
● Aka: inclusion polymorphism
Actually, the message/method distinction implies subtypingbased polymorphism
9
Subclassing a Point Class
public class Point2d {
private int x, y;
public Point2d(int x0, int y0) { x = x0; y = y0; }
public int getX() { return x; }
public int getY() { return y; }
public int moveRight(int dx) { x += dx; }
public int moveUp(int dy) { y += dy; }
public String toString() { return "[" + x + "," + y + "]"; }
public boolean isOrigin() { return x == 0 && y == 0; }
}
public class Point3d extends Point2d {
private int z;
public Point3d(int x0, int y0, int z0) {
super(x0, y0); z = z0; }
public int getZ() { return z; }
public int moveIn(int dz) { z += dz; }
public String toString() {
return "[" + x + "," + y + "," + z + "]"; }
public boolean isOrigin() {
return super.isOrigin() && z == 0; }
}
10
Three Choices
public class Point3d extends Point2d {
private int z;
public Point3d(int x0, int y0, int z0) {
super(x0, y0); z = z0; }
public int getZ() { return z; }
public int moveIn(int dz) { z += dz; }
public String toString() {
return "[" + x + "," + y + "," + z + "]"; }
public boolean isOrigin() {
return super.isOrigin() && z == 0; }
}
●
Three options for every member in a subclass
– Introduced by the subclass: int z, getZ(), moveIn()
– Inherited from the superclass: getX(), getY(), ...
– Redefined by the subclass: isOrigin(), toString()
11
Redefined Methods in Point3d
public class Point3d extends Point2d {
...
public String toString() {
return "[" + x + "," + y + "," + z + "]";
}
public boolean isOrigin() {
return super.isOrigin() && z == 0;
}
}
...
Point2d p = new Point3d(0,0,10);
p.isOrigin(); // Result: False
●
Redefined members are the most interesting ones
– We have two competing definitions
– The language should define the semantics (i.e.: who's winning?)
– Java's semantics for methods is overriding
12
Overriding
Point2d p = new Point3d(0,0,10);
p.isOrigin(); // Result: False
●
●
●
●
Message: isOrigin()
Method: Point3d.isOrigin()
There are two types associated with a receiver:
1)Static type: type of the variable (here: Point2d)
The type that is known at compile time
2)Dynamic type: type of the pointed object (here: Point3d)
This type is known only at run-time
– If we have subtyping, then it is possible that:
static type ≠ dynamic type
Two approaches for looking up a method from a message:
– Static binding: Use the static type
● Lookup is carried out at compile-time
– Dynamic binding: Use the dynamic type
● Lookup is carried out at run-time
● Compiler does not know what method is invoked
13
Binding Quiz
public class X {
public String toString() { return "X"; }
// final means non-overrideable
public final int addThree(int n) { return n + 3; }
public static int mul(int a, int b) { return a * b; }
}
public class Y extends X {
public final String toString() {
return "Y" + super.toString();
}
}
X x = new X();
...
X x = new Y();
x.toString();
...
x.addThree(5);
x.toString();
x.mul(3,9);
x.addThree(5);
x.mul(3,9);
●
●
Y y = new Y();
...
y.toString();
y.addThree(5);
y.mul(3,9);
What's the binding of each call?
What's the binding of super.toString()?
14
Refinement
public class Point3d extends Point2d {
public String toString() {
return "[" + x + "," + y + "," + z + "]";
}
public boolean isOrigin() {
return super.isOrigin() && z == 0;
}
}
●
●
Point3d.isOrigin() uses Point2d.isOrigin()
– Such a method is said to be a refinement of its overridden
method
– Further eliminates code duplication
● Compare with: return x == 0 && y == 0 && z == 0
Can toString() become a refinement?
– Yes
15
Variations on Refinement
●
●
●
precursor(...)instead of super.methodName(...)
– Eiffel has that
Alpha refinement: overriding method calls overridden method
– Java, C#, Eiffel, LST, ...
Beta refinement: overridden method calls overriding method
– Pros:
● Easier to ensure invariants
– Cons:
● Designer of superclass must think of subclasses in advance
● What's the semantics if no subclass exists?
// Pseudo code: Beta-refinement in Java
public class X {
public int n;
public void print() {
System.out.println("n=" + n);
inner; // New keyword, invokes overriding method
}
}
16
Variations on Refinement (cont.)
●
●
Daemons
– A method definition has three parts:
● A “before” daemon; a body; An “after” daemon
– Before daemons are invoked on a top-first order
– After daemons are invoked on a bottom-first order
Method combinators: Combining return values
– From all overridden methods (in Alpha refinement)
– From all overriding methods (in Beta refinement)
– Typical combinators: add, append-to-list, append-to-set, ...
17
Subclass Affects Superclass
public class Adder
public int a, b;
{
public int add() { return a + b; }
public void printResult() { System.out.println(add()); }
}
public class Subtracter extends Adder {
public int add() { return a - b; }
}
...
Adder adder = new Subtracter();
adder.a = adder.b = 9;
adder.printResult(); // Output: "0"
●
●
●
This program is legal but confusing
– adder.printResult()prints the difference ?!
Also, sometimes a superclass cannot provide an implementation
for a method
Solution: abstract methods
18
Abstract Methods
●
●
●
●
19
An override-able method can be declared as abstract
– Usually, this means that it has no body
The resulting class is incomplete
– It's protocol contains a certain message, with which no behavior
is associated
– We expect a subclass to complete the missing behavior
– => In a statically typed language the compiler will not allow such
classes to be instantiated
Terminology:
– Abstract class
– Concrete class
A subclass of an abstract class:
– Is concrete if it defines overriding methods for all inherited
abstract methods
– Otherwise, it is abstract as well
Subclass Affects Abstract Superclass
public abstract class Operation
protected int a, b;
20
{
public abstract int compute();
public void printResult() { System.out.println(compute()); }
}
public class Adder extends Operation {
public Adder(int x, int y) { a = x; b = y; }
public int compute() { return a - b; }
}
public class Subtracter extends Operation {
public Subtracter(int x, int y) { a = x; b = y; }
public int compute() { return a - b; }
}
...
Operation o = new Adder(9, 9);
o.printResult(); // Output: 18
o = new Subtracter(9, 9);
o.printResult(); // Output: 0
Subclass Introduces a Bug
public class Range {
protected int b, e;
public Range(int x, int y) { begin(x); end(y); }
public void begin(int n) { b = n; }
public void end(int n) { e = n; }
public void move(int n) { begin(b+n); end(e+n); }
}
public class PositiveRange() {
public PositiveRange(int begin, int end) {
super(begin, end); check(); }
private void check() {
if(e <= b) throw new Exception("Invalid range");
}
public void begin(int n) { super.begin(n); check(); }
public void end(int n) { super.end(n); check(); }
}
21
Strict Inheritance
●
●
●
The overriding mechanism is very powerful
– One can selectively replace parts of a superclass
– As we've just seen it is almost too powerful...
– Problem 1: overriding method expects too much
– Problem 2: overriding method delivers too little
A partial solution: Strict inheritance
– A limited form of inheritance
– Only two choices: introduce or inherit.
● No redefinition!
– Eliminates the above-mentioned problems
– Not as useful
Most languages do not provide an explicit mechanism for strict
inheritance
– You can always use subclassing and just avoid redefinition
22
Download