Monday, May 27, 2013

Liskov substitution principle

Recently I had a chance to upgrade a software component and get a better understanding about Liskov substitution principle.  Here I want to share this experience.

First I want to give the clear definition of Liskov principle.  This is the original Liskov principle definition:

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

The following is from Wiki:

Liskov substitution principle:  objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

The famous violation to this principle is the Circle-ellipse problem (or similar square-rectangle problem).  You can find the detail information in the Wiki definition.  

Scenario

The component creates and manages our software license.  Basically the code handles a chunk of binary memory, and the .NET BitArray is a good start.  BitArray treats every bit as a boolean value and kind of changes managing memory to managing a list of boolean values, so it makes managing binary memory easier.  But since BitArray is sealed, we cannot extend, so our base class just wraps up the BitArray class and adds some overloaded methods to make the operations easier.  The base class looks like this:


    public class CommonBits
    {
        private BitArray _bits;

        public void Set (int index, bool value)  { }
 
       public void Set (int index, byte value) { }         
        public void Set (int index, int value) { }
        ...

        public bool Get (int index) { }
        public int GetInt (int index) { }
        ...
 
    }

The derived class adds some secure operations to the base class.  The derived class still handles the binary memory, but it adds CRC verification to the original bits array.  The interface is like the following:


    public class SecureBits : CommonBits
    {
        public void Encode()  { }
 
       public void SetCRC() { }
 
       public void GetCRC() { }
 
    }

SecureBits prefixes 32-bits CRC checksum to the original BitArray.  We can use the following graph to demonstrate the difference between the 2 classes:

    

Problem
Now comes the problem.  Let's suppose one user uses the CommonBits class, he/she just would simply do this:

        var bits = new CommonBits();
        bits.Set (1, true);
        bits.Set (100, false);


However, if you replace the above code with the derived class SecureBits, the problem comes.  Since the first 32 bits data are calculated based on the following actual data, user cannot set any of the bit individually.  The Set() method in the derived class should be something like this:
        public void Set (int index, bool value)
        {
            if (index < 32)
                throw new Exception ("The CRC value cannot be set.  The value should be automatically calculated based on your actual bit array.");
            ...
        }
  

Apparently this code already violates Liskov principle "no new exceptions should be thrown".


Solution

My solution is change the inheritance to composition, which coincides with "favor composition over inheritance" principle.  The code is something like this
:

    public class SecureBits
    {
        public int CRC32Value {
            get
            {
                return CalculateCRC();
            }
            private set {}
        }
        public 
CommonBits BitsData { get; set; }        
        public CommonBits ToCommonBits() { }
    
}

From the BitsData property, user can access all CommonBits operations, but he/she cannot set the CRC value.  Also I provide ToCommonBits() method and user can converts the instance to a normal CommonBits instance.  But from there on, if user changes some bits, the CRC value will not change accordingly.  But it's useful when user wants to use other methods of CommonBits.

No comments: