Quote for the Week

"Learn to enjoy every moment of your life"

Wednesday, January 28, 2015

S.O.L.I.D Architecture principle in C# (Part 3)

Continue to Tuesday, January 27, 2015
----------------------------------------------

Liskov Substitution Principle (LSP) :


Liskov Substitution Principle or LSP states that:

Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.

You must be aware that you can use an object of a derived class anywhere where base class is expected. However, when such a substitution occurs the functionality and correctness of the code shouldn't change. Let's understand this with an example. Suppose you have created a class named SpecialCustomers that maintains a list of special Customers (may be they are special because they are frequent buyers, or they purchase high value items). This class is shown below:

public class SpecialCustomers
{
    List<Customer> list = new List<Customer>();
 
    public virtual void AddCustomer(Customer obj)
    {
        list.Add(obj);
    }
 
    public int Count
    {
        get
        {
            return list.Count;
        }
    }
}

The SpecialCustomers class maintains a List of Customer instances (Customer class is not shown in the code for the sake of simplicity). The AddCustomer() method accepts an instance of Customer and adds to the generic List. The Count property returns the number of Customer elements in the List.

Now, let's assume that you create another class - TopNCustomers - that inherits from the SpecialCustomers class. This class is shown below:

public class TopNCustomers:SpecialCustomers
{
    private int maxCount = 5;
 
    public override void AddCustomer(Customer obj)
    {
        if (Count < maxCount)
        {
            AddCustomer(obj);
        }
        else
        {
            throw new Exception("Only " + maxCount + " customers can be added.");
        }
    }
}

The TopNCustomers class overrides the AddCustomer() method of the SpecialCustomers base class. The new implementation checks whether the customer count is less than maxCount (5 in this case). If so, the Customer is added to the List else an exception is thrown.

Now, have a look at the following code that uses both of these classes.

SpecialCustomers sc = null;
sc = new TopNCustomers();
for (int i = 0; i < 10; i++)
{
    Customer obj = new Customer();
    sc.AddCustomer(obj);
}

The code declares a variable of type SpecialCustomers and then points it to an instance of TopNCustomers. This assignment is perfectly valid since TopNCustomers is derived from SpecialCustomers. The problem comes in the for loop. The for loop that follows attempts to add 10 Customer instances to the TopNCustomers. But TopNCustomers allows only 5 instances and hence throws an error. If sc would have been of type SpecialCustomers the for loop would have successfully added 10 instances into the List. However, since the code substitutes TopNCustomers instance in place of SpecialCustomers the code produces an exception. Thus LSP is violated in this example.

To rectify the problem we will rewrite the code like this:

public abstract class CustomerCollection
{
    public abstract void AddCustomer(Customer obj);
    public abstract int Count { get; }
}
 
public class SpecialCustomers:CustomerCollection
{
    List<Customer> list = new List<Customer>();
 
    public override void AddCustomer(Customer obj)
    {
        list.Add(obj);
    }
 
    public override int Count
    {
        get
        {
            return list.Count;
        }
    }
}
 
public class TopNCustomers : CustomerCollection
{
    private int count=0;
    Customer[] list = new Customer[5];
 
    public override void AddCustomer(Customer obj)
    {
        if(count<5)
        {
            list[count] = obj;
            count++;
        }
        else
        {
            throw new Exception("Only " + count + " customers can be added.");
        }
    }
 
    public override int Count
    {
        get
        {
            return list.Length;
        }
    }
}

Now, the code has CustomerCollection abstract class with one property (Count) and one method (AddCustomer). The SpecialCustomers and TopNCustomers classes inherit from this abstract class and provide the concrete implementation for the Count and AddCustomer(). Notice that in this case TopNCustomers doesn't inherit from SpecialCustomers.

The new set of classes can then be used as follows:

Customer c = new Customer() { CustomerID = "ALFKI" };
CustomerCollection collection = null;
collection = new SpecialCustomers();
collection.AddCustomer(c);
collection = new TopNCustomers();
collection.AddCustomer(c);

The above code declares a variable of CustomerCollection type. Once it points to an instance of SpecialCustomers and then to TopNCustomers. In this case, however, their base class is CustomerCollection and the instances are perfectly substitutable for it without producing any inaccuracies.

No comments: