S.O.L.I.D Principles for beginners (Part 3)

Before you start reading this blog post, please read Part 1 and Part 2 of the series, if you have not read it.

Liskov Substitution Principle
Definition: The derived classes extending the base classes should not change their behavior

Scenario: Let's discuss the same example for Mr.X. On processing shipment, Mr. X needs the agent details and international agent should accept discount. What if we create an interface IAgent with Name, Location and Discount and inherit IAgent to Local agent class and International agent class? When we Process shipment, we need to add a condition for international shipment.
if (shipmentType == "International")
{
return amount * discount;
}

This is changing the behavior of the class, say we need to have a third type of shipmentType, the conditions in the ProcessShipment method will get increased and the class will be complicated.

Solution:
Add a new interface IShipment, which has ProcessShipment method.
e.g:

public interface IShipment
{
        void ProcessShipment(ICustomerDetails customerDetails);
}

The IShipment class is then implemented to Local and International shipment classes. If a third type of shipment comes, then IShipment interface can be implemented for the new class as well.

/// <summary>
/// Adding IShipent interface is considered as an open close principle and
/// adding ICustomer details as parameter is to avoid violating Single Responsibility of shipment
/// </summary>
public class LocalShipment : IShipment
{
    public void ProcessShipment(ICustomerDetails customerDetails)
    {
        Console.WriteLine("Process local shipment for " + customerDetails.CustomerName);
    }
}

/// <summary>
/// Liskov Substitution Principle: Derived classes should extend the base classes without changing their behavior
/// Adding  a new parameter Discount in internationl shipment is a classic example of
/// Liskov Substitution Principle; Here there is a condition before processing.
/// On all cases, ProcessShipment should work and since AgentName is mandatory,
/// modify the class that implements IShipment to accept AgentName before
/// processing the shipment, instead of not processing.
/// </summary>
public class InternationalShipment : IShipment
{
    private InternationalAgent Agent { get; set; }

    public InternationalShipment(InternationalAgent agent)
    {
        // This is an example of Dependency Inversion Principle
        // Since LSP is implemented in the example,
        // null reference exception is not checked
        this.Agent = agent;
    }

    public void ProcessShipment(ICustomerDetails customerDetails)
    {
        if (this.Agent == null)
        {
            // Derived classes should extend the base classes without changing their behavior
            // If we return from the method, the base class behavior is changed.
            // i.e Process shipment has not achieved its functionality
            // This is for illustration purpose.
            // In short, Once you call the method ProcessShipment, it should process completely
            this.Agent = new InternationalAgent();
        }

        Console.WriteLine("Process international shipment for " + customerDetails.CustomerName + " and the agent is "
            + this.Agent.AgentName + " and dicount is " + this.Agent.Discount + "%");
    }
}

Comments