Code Contracts and Inheritance
The Liskov Substitution Principle
This is the third post on a mini-series about Microsoft Code Contracts and why you should use it to write more robust and reusable code.
After introducing pre and post-conditions and invariants, it’s now time to discuss one of the most powerful concepts of the Design by Contract, that is contracts inheritance. The importance of the interactions between contracts and inheritance is described in one of the cornerstone concepts of Object Oriented Programming, that goes under the name of the Liskov Substitution Principle:
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
This is a definition of how polymorphism through inheritance is meant to work: it must always be possible to use an instance of a child class in lieu of a parent class without the client knowing about it.
Let’s make a simple example. Suppose we define a service to reverse the content of a string. We can have a simple implementation of that class in the following terms:
Notice how the Reverse
method has been defined as virtual. This makes it possible to subclass and replace the implementation of the method with an algorithm that is faster (possibly trading off some memory; we are not concerned here with the nitty-gritty details of the actual
implementation).
We introduce a factory whose responsibility is creating an object that knows how to reverse a string:
Finally, we have client class that, for whatever reason, needs to reverse a string.
In this case the Client
class uses (unknowingly) an instance of a StringReverser
class (which is the base class listed above). Since FastStringReverser
is a subclass of StringReverser
, if we rewrite the factory to return an instance of FastStringReverser
, Client will continue to work:
So here we see the LSP in action. Referring to the definition at the top of this post, we have the following correspondences:
StringReverser
represents type T (the base type)FastStringReverser
represents type S (the sub type)Client
represents P (the code using T or a subtype of T)
Note: here I have presented the example subclassing from a base class with a virtual method to match more closely the standard definition of the LSB. Even more common, though, is that T represents an interface and that two or more concrete classes implement that interface. The client will have a handle to such an interface and the actual implementation will be provided either by a factory method, service locator or through dependency injection.
The LSP is violated, for example, whenever a client class has some logic dealing with the particular type of the implementation (such as Run Time Type Identification: if the actual type is this, then do that, otherwise do that other thing - or something along this line).
LSP and Contracts
Unfortunately, there are much more subtle ways in which the LSP can be violated, even if the client code is "well behaving" as in the example above. The classical example goes under the name "A Square is Not A Rectangle". According to geometry, a square is a rectangle, but when it comes to software design... it is not!
The basic problem is that a square has an additional invariant with respect to the
rectangle, which is that all the sides are equal and because of that you cannot independently
change the height and the width like you would do with a rectangle; so if you have
a SetHeight
and SetWidth
methods in the Rectangle,
the Square should override those so that the invariant is always maintained: when the
height is set by a client, internally the width is also set and vice versa. But the
client might not expect this to happen! The client might make assumptions like the following:
It's easy to see that if the factory returns a Square, which internally always adjust one side to be equal to the other one, some of the assertions would fail. This means that the expectations of the client of dealing with a Rectangle square are no longer met, and the LSP is thus violated.
A way to make it clear what really "being a rectangle" is meant to be is to introduce some contracts on the base class:
Now, when it comes to inheriting from a class which specify some contracts, Design By Contract specifies the following behavioral subtyping:
- If a subclass overrides the behavior of a method of a base class (or implements and interface), the preconditions it specifies can only be equivalent to or weaker than the base class
- If a subclass overrides the behavior of a method of a base class (or implements and interface), the postconditions it specifies can only be equivalent to or stronger than the base class.
This mean that any potential subclass of Rectangle
needs to satisfy
the postconditions in each of his methods (and possibly add more). This explicitly rules out Square
from being a candidate subclass (since a square cannot guarantee that changing one side does not affect the other).
It's interesting to note that, quite symmetrically, the preconditions of a base class can only be weaker; this makes sense especially from the client perspective: if subclasses were allowed to require more stringent preconditions, a client might fail to meet some precondition and thus stop working when a service implementation is substituted with another, which is a clear violation of the LSP, which is all about transparency.
Specifying Contracts for Interfaces and Abstract Classes
In the example above, Rectangle
was a concrete class with some virtual method.
But how do you specify contracts with Microsoft Code Contracts for interfaces or abstract
methods which do not have a concrete implementation?
For interfaces, you need to create a dummy class implementing the interface and link them through Attributes, like the following:
Note how you have to use explicit interface implementation and the use of a dummy return value.
For abstract methods it's similar:
I urge you to refer to Microsoft Code Contracts documentation for more information on how to specify contracts for interfaces and abstract methods.
Leave a Comment