Liskov Substitution Principle of Object Oriented Design

来源:百度文库 编辑:神马文学网 时间:2024/04/27 20:08:29
The Liskov Substitution Principle
The Liskov Substitution Principle of object oriented design states:
In class hierarchies, it should be possible totreat a specialized object as if it were a base class object.
The basic idea here is very simple. All code operating with a pointer orreference to the base class should be completely transparent to the type of theinherited object. It should be possible to substitute an object of one type withanother within the same class hierarchy. Inheriting classes should not performany actions that will invalidate the assumptions made by the base class.
This is best explained with an example. The following example explains a casewhere enhancements to the code can violate the Liskov Substitution Principle.The discussion is divided into three steps:
Original Code
Brand-C Support Code Enhancements
Problems (Violates Liskov Substitution Principle)
We will consider the design of software that manages the temperature invarious chambers in a system. The software periodically reads the temperaturefrom each chamber and then adjusts it to a reference temperature. The behavioris modeled as a Temperature Controller base class. Temperature controllers indifferent chambers differ in their programming interface. These differences arehandled by individual classes that inherit from Temperature Controller baseclass.
The Temperature Controller base class supports the following methods:
Get/Set Reference: These methods are used to get and set the reference temperature for the chamber. This is not a virtual method, as no device programming is involved.
Get Temperature: Reads the temperature from the device. Since registers for reading the temperature differ from one device to another, this method is pure virtual.
Adjust Temperature: Adjusts the temperature by applying the adjustment specified in the parameter. Again, this method is pure virtual as it involves device programming.
The following code also shows two classes inheriting from the base class.
Temperature Controller
class TemperatureController { // The chamber needs to be maintained at the reference temperature int m_referenceTemperature; public: int GetReferenceTemperature() const { return m_referenceTemperature; } void SetReferenceTemperature(int referenceTemperature) { m_referenceTemperature = referenceTemperature; } virtual int GetTemperature() const = 0; virtual void AdjustTemperature(int temperature) = 0; virtual void Initialize() { // Initialize the device address here } }; class Brand_A_TemperatureController { public: int GetTemperature() const { return (io_read(TEMP_REGISTER)); } void AdjustTemperature(int temperature); { io_write(TEMP_CHANGE_REGISTER, temperature); } }; class Brand_B_TemperatureController { public: int GetTemperature() const { return (io_read(STATUS_REGISTER) & TEMP_MASK); } void AdjustTemperature(int temperature); { // Device requires shifting by 5 bits before writing to the change // register io_write(CHANGE_REGISTER, temperature << 5); } };
Now consider the case where the marketing department comes back and says theyneed support for another type of Temperature Controller - Brand C. Thedevelopers assume that this should be a simple change as all they need to do isinherit from Temperature Controller base class and define the Get Temperatureand Adjust Temperature methods.
On further inspection of the programminginterface, the developers realize that Brand C is quite different from the otherTemperature Controllers. It does not fitwell into their scheme of things. Brand C is an automatic device where thereference temperature is programmed to the device and then on the deviceautomatically maintains the temperature to the desired level.
It is clear thatthat Brand C will not fit into the base class. Thus developers decide to changethe base class by making Get/Set Reference Temperature methods virtual (not purevirtual). They figure this way all the other temperature sensors would work withexisting base class implementation. The Brand C would override the Get/SetReference Temperature methods. These methods would directly operate upon thedevice.
Another change neededwould be to override Adjust Temperature method with a blank implementation. Asthis method has no role to play in Brand C (Brand C is automatic so it performstemperature adjustments on its own.).
The final code is shown below: Temperature Controller
class TemperatureController { // The chamber needs to be maintained at the reference temperature int m_referenceTemperature; public: // Get and Set methods for Reference Temperature have been // made virtual to accomodate Brand C virtual int GetReferenceTemperature() const { return m_referenceTemperature; } virtual void SetReferenceTemperature(int referenceTemperature) { m_referenceTemperature = referenceTemperature; } virtual int GetTemperature() const = 0; virtual void AdjustTemperature(int temperature) = 0; }; class Brand_C_TemperatureController { public: // Get/Set Reference Temperatures now read and write the device directly int GetReferenceTemperature() const { return (io_read(REFERENCE_REGISTER); } void SetReferenceTemperature(int referenceTemperature) { io_write(REFERENCE_REGISTER, referenceTemperature); } int GetTemperature() const { return (io_read(TEMP_MONITORING_REGISTER)); } void AdjustTemperature(int temperature); { // Adjust temperature has no role in brand C, as temperature // control is automatic } };
The problems with the above design are:
It is a band-aid solution to the problem. A more natural solution would be to define a base class for Automatic Temperature Sensors.
It violates the Liskov Substitution Principle. We can no longer substitute one class from the class hierarchy with another.
One can easily see the following violations of the Liskov SubstitutionPrinciple. Consider the code below that is operating with a pointer to theTemperature Controller. The code first sets the reference temperature and thenintializes the controller. This code would work fine if pTempCtrl was pointingto a Brand A or B temperature controller. The code breaks when the pointer isBrand C. This happens because of the override of SetReferenceTemperature nowaccesses the device using a io_write call. But the code actually callsinitialize only in the following statement. Thus all temperature controllers arenot perfectly substitutable. The SetReferenceTemperature method for Brand A andB does not access the device. The same method for Brand C accesses the device.
SetReferenceTemperature violates Liskov Substitution Principle
. . . TemperatureController *pTempCtrl = GeNextTempController(); pTempCtrl->SetReferenceTemperature(10); pTempCtrl->Initialize(); . . .
Code calling Adjust Temperature may break too. If the original code wasbeing used to set the temperature to any thing other than the referencetemperature, it will not have the desired effect with Brand C. This method hasbeen overriden for Brand C to perform no action.
If Liskov Substitution Principle is followed, code using a base class pointerwill never break after another class is added to the inheritance tree.
Related Links
Object Oriented Tips I
Object Oriented Tips II
Open Closed Principle