Difference between Inheritance and Subtyping
We say that a subclass is inheritance and subtyping. We have studied subtyping in Typing and Subtyping and it entails mainly two things:
- Liskov Substitution
- Polymorphism of the types
While inheritance is just the code reuse and specialization.
Another example of code reuse can be the “has-a” relation composition, which couples two objects together.
With aggregations, we can also see delegation calls, when you have a class, and call a method of that class.
Type relations between Mutable and Immutable
The cleanest solution between these two methods is just to separate the interfaces since:
- If Mutable < Immutable, we violate behavioural subtyping.
- If Immutable < Mutable, we violate the syntactic subtyping, since mutable is wider interface usually (not always!).
Java has a bad design because Immutable < Mutable.
Separation can be done in two ways usually:
- Aggregation with delegation (this is not a bad idea actually).
- This solution might lead to lots of boilerplate code.
- Access methods for local methods.
- Common superclass.
- We can also inherit without subtyping (like C++ does support that with private or protected inheritance, but not safely, also Eiffel).
- Inheritance might be sometimes unnecessary.
Suggestions
- We should not change to dynamically-bound methods, this can lead to many kinds of unexpected behaviours.
- We should not inherit from classes that change often.
▪ Use subclassing only if there is an “is-a” relation- Syntactic and behavioral subtypes▪ Do not rely on implementation details- Use precise documentation (contracts where possible)▪ When evolving superclasses, do not mess aroundwith dynamically-bound methods- Do not add or remove calls, or change order of calls▪ Do not specialize superclasses that are expected tochange often
Method Resolution
Java:
- Chooses most type specific method among the hierarchy
C#
- This is bound statically.
Binary Methods
For standard binary methods like equal to, we would like the subtype to have the same parameter of itself, but we know that this is not typesafe. We now want some methods to resolve these.
- We could make explicit type tests, and get the more general value, for example:
boolean equals(Object o) {
if (o instanceof Circle) {
Circle c = (Circle) o;
// check equality
} else return false;
}
- Double invocation, also called the visitor patter, see Design patterns.
class Shape {
Shape intersect( Shape s ){
return s.intersectShape( this );
}
Shape intersectShape( Shape s ){
// general code for all shapes
}
Shape intersectRectangle( Rectangle r ){
return intersectShape( r );
}
}
- Overloading and Dynamic resolution of the method, but we are not solving the typesafety problem.
class Shape {
Shape intersect( Shape s ) {
// general code for all shapes
}
}
class Rectangle : Shape {
Rectangle intersect( Rectangle r ) {
// efficient code for two rectangles
}
}
static Shape intersect( Shape s1, Shape s2 ) {
return( s1 as dynamic ).intersect( s2 as dynamic );
}
In this case, the method is resolved to the Rectangle only if both methods are Rectangle, if any of them, or if I remove the dynamic resolution of any of the methods then, it will call the Shape method.