This principle is complementary to the Open Closed Principle (OCP). It suggests that methods which use a certain class must be able to use its derived classes without even knowing them - a base class must be substitutable by its derived classes, without the necessity of changing any code in the method using it.
There are two violations of this principle: one more conceptual and the other more functional.
The conceptual violation is most of the time easy to detect because it violates the Open Closed Principle (OCP) at the same time. It consists of determining the concrete types of objects to do the adapted treatment in methods. Verifying concrete types or using casts may indicate this violation.
The following code example shows such a case :
In this example the PrintShapeArea(…) method needs to know the concrete type of the passed object for being able to call the adapted method which returns the correct shape area.
When a new shape type needs to be added, the PrintShapeArea(…) method needs to be modified (violation of OCP). Furthermore object that are used in this context get tightly coupled to the class that contains the PrintShapeArea(…) method.
To prevent this kind of violation the usage of cast, is, as and GetType (working with object types) must be avoided at a maximum.
A good solution is to apply the Open Closed Principle (OCP) as already explained. If we take the same class design from last time, we need to add a GetArea(…) method to the abstract base class Shape. The derived classes need to override this method with the correct shape area calculation.
The PrintShapeArea(…) method can now be simplified and is now able to handle all derived classes transparently without knowing them. The base class and its derived classes get substitutable.
The functional violation is much harder to find because it is not due to an error in class design but a modification of internal behavior. Methods may expect certain behaviors of classes they use. Derived classes may change this behavior and the results of operations may differ from the expected results.
The solution to this problem is the Design By Contract approach. This approach assures that functional constraints are respected all the time. It assures that the initial behavior stays the same and that objects always have the correct states.
There are three types of contracts:
- pre-conditions: constraints and state before treatment
- post-conditions: constraints and state after treatment
- invariants: concerned objects must not change during treatment
The necessary classes and methods for the Design By Contract approach are already integrated in .NET Framework 4.0 in the CodeContracts namespace System.Diagnostics.Contracts. But they can easily be added in older versions of.NET.