So reading and writing operations are fundamentally different with regards to inheritance.
Going back to OOP 101, remember the parable about the square and the rectangle? It is often said that the square is a kind of rectangle, but that is only true when reading.
When writing, squares are not kinds of rectangles, but rectangles are kinds of squares!
Ie:
bool test( Rectangle* r ) {
int old_height = r->GetHeight();
int old_width = r->GetWidth();
r->SetWidth(old_width+100);
return old_height == r->GetHeight();
}
the above function returns true
for all "real" rectangles, but for Squares
it might not. So the contracts around SetWidth
that are reasonable for Rectangle
is violated for Square
.
On the other hand, every interface for Rectangle
that is read-only is perfectly handled by Square
.
This might give you a mess like this:
struct IRectangleRead { ... };
struct ISquareRead { ... };
struct ISquareWrite: virtual ISquareRead { ... };
struct IRectangleWrite:ISquareWrite, virtual IRectangleRead { ... };
struct ConstRectangle: virtual IRectangleRead { ... };
struct ConstSquare: virtual ISquareRead, virtual IRectangleRead { ... };
struct Rectangle: ConstRectangle, IRectangleWrite { ... };
struct Square: ConstSquare, ISquareWrite { ... };
which generates a mess of an inheritance hierarchy, but one where restrictive contracts can be placed on each method, and every object that implements the method will obey them.
Now, you should note that the above becomes ridiculously easier if your objects are immutable. Then the only form of writing is via factory functions, and things become tidy.
So the concrete lesson here -- split the reading and modifying parts of your code. The common modifying part (that works on the base classes) isn't publicly exposed, because the operation is invalid in the subclass cases.
The common reading part is publicly exposed, as is the subtype reading part.
The subtype writing code forwards to the private common base class writing code.