Liskov Substitution Principle

Definition:

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

  • Derived classes should be substitutable for their base classes.
  • Methods that use references to base classes have to be able to use methods of the Derived classes without knowing about it or knowing the details.

Description:

The derived class should not break the functionality of the base class. Liskov Substitution Principle(LSP) allows to create maintainable class hierarchies with the help of Inheritance. LSP helps us to use subclasses and base classes interchangeably without knowing the details of eachother.

What is the benifit of designing a program that supports subclasses and base classes are interchangeable(substitutable)?

When subclasses and base classes are interchangeable(substitutable), then the relation between baseclass and subclasses are set properly and subclasses behavior is not breaking the behaviour of baseclass.

What is the need of LSP ?

The Liskov Substitution Principle is a way of ensuring that inheritance is used correctly.

Example for LSP Vialoation:

LSP Vialoation

Meal : class with meal relevant properties.

HomeMenuRepository : Class is a base class for adding meal information into home menu repository.

RestaurantMenuRepository : Derived class for adding meal information into restaurant menu repository.

LspViolation : RestaurantMenuRepository class inherited from RestaurantMenuRepository. Adding meal into HomeMenuRepository is not having any preconditions.But price is mandatory for adding meal into RestaurantMenuRepository as a precondition.

public enum CuisineType
{
    American = 1,
    Asian = 2,
    European = 3,
    Oceanian = 4
}

public enum MealType
{
    Vegeterian = 1,
    NonVegeterian = 2
}

public enum SpiceLevel
{
    Low = 1,
    Medium = 2,
    High = 3
}

public class Meal
{
    public string MealName { get; set; }
    public MealType MealType { get; set; }
    public CuisineType CuisineType { get; set; }
    public float? Price { get; set; }
}

public class HomeMenuRepository
{
    public virtual void AddMeal(Meal mealInfo)
    {
        //Price shall be null for adding HomeMenu into database repository
        Console.WriteLine("Meal is added into repository");
    }
}

public class RestaurantMenuRepository : HomeMenuRepository
{
    public override void AddMeal(Meal mealInfo)
    {
        //Price must be greater than 0 for adding RestaurantMenu into database repository
        if(mealInfo.Price == null || mealInfo.Price <= 0)
        {
            throw new Exception("Price must be greater than 0");
        }
    }
}

class LspViolation
{
    static void Main(string[] args)        
    {
        Meal mealInfo = new Meal();
        mealInfo.MealName = "Scrambled Eggs";
        mealInfo.MealType = MealType.NonVegeterian;
        mealInfo.CuisineType = CuisineType.American;

        //Price is not mandatory for adding menu into HomeMenu
        HomeMenuRepository homeMenu = new HomeMenuRepository();
        homeMenu.AddMeal(mealInfo);

        //Price is mandatory for adding menu into Restaurant Menu
        //Parent class behavior is differnt than child class behavior for adding meal into repository 
        //Price must be greater than zero for adding restaurant menu into repository
        HomeMenuRepository restaurantMenu = new RestaurantMenuRepository();
        restaurantMenu.AddMeal(mealInfo);

        Console.ReadLine();
    }
}
        

Inheriting derived class(RestaurantMenuRepository) from base class(HomeMenuRepository) relation is not proper since its derived class(RestaurantMenuRepository) behavior is differnt than base class(HomeMenuRepository) behavior due to derived class precondition(Price must be greater than Zero). This is vialoation LSP if we use baseclass and derived class interchangeably.

Fix for LSP:

Fix for LSP Vialoation

MenuRepository : Abstract base class with AddMeal abstract method.

HomeMenuRepository : Derived class for adding meal information into restaurant menu repository.

RestaurantMenuRepository : Derived class for adding meal information into restaurant menu repository.

Lsp : HomeMenuRepository & RestaurantMenuRepository class inherited from MenuRepository. Adding meal into HomeMenuRepository is not having any preconditions. But price is mandatory for adding meal into RestaurantMenuRepository as a precondition. However, this precondition is not affecting the inheritance behavior due to MenuRepository abstract class.

public abstract class MenuRepository
{
    public abstract void AddMeal(Meal mealInfo);
}

public class HomeMenuRepository : MenuRepository
{
    public override void AddMeal(Meal mealInfo)
    {
        //Price shall be null for adding HomeMenu into database repository
        Console.WriteLine("Meal is added into repository");
    }
}

public class RestaurantMenuRepository : MenuRepository
{
    public override void AddMeal(Meal mealInfo)
    {
        //Price shall be null for adding HomeMenu into database repository
        if (mealInfo.Price == null || mealInfo.Price <= 0)
        {
            Console.WriteLine("Price must be greater than 0");
        }
    }
}

class Lsp
{
    static void Main(string[] args)
    {
        Meal mealInfo = new Meal();
        mealInfo.MealName = "Scrambled Eggs";
        mealInfo.MealType = MealType.NonVegeterian;
        mealInfo.CuisineType = CuisineType.American;

        //Price is not mandatory for adding menu into HomeMenu
        MenuRepository homeMenu = new HomeMenuRepository();
        homeMenu.AddMeal(mealInfo);

        //Price is mandatory for adding menu into HomeMenu
        //But inherited from MenuRepository abstract class not from HomeMenuRepository
        MenuRepository restaurantMenu = new RestaurantMenuRepository();
        mealInfo.Price = 10;
        restaurantMenu.AddMeal(mealInfo);

        Console.ReadLine();
    }
}