Rob Sutherland’s Musings on Life, Code, and Anything Else

LSP: Liskov Substitution Principle

This is the L of the SOLID series.

Working definition: "objects of a super class should be replaceable with objects of its sub classes without breaking the application."

Let's look at a simple example.

class User {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public virtual string DispalyName() => string.Concat(" ", FirstName, LastName);
}

public Student : User {
    public int Age { get; set; }
}

LSP says that any thing in the system that uses User should also be able to use Student without any issues.

Let's see what happens when we break LSP.

public Student : User {
    public override string DisplayName() => throw new Exception("not implemented");
}

We break the LSP because now Student will throw an exception when calling DisplayName(). The system has no way of knowing that Student is now no longer a viable replacement for User. Inheritence says that it should be usable, but implementation breaks functionality. By violating LSP we bring risk into the system.

LSP applies to interfaces as well. Take the following definition, usage and implementation.

public interface IStudentProfileReader 
{
    Student Get(int id);
    IEnumerable<Student> FindByEmail(string email);
}

public class StudentInfoController : Controller 
{
    private readonly IStudentProfileReader _reader;
    public StudentInfoController(IStudentProfileReader reader) => _reader = reader;

    [HttpGet("/search")]
    public IActionResult FindByEmail(string email) => View(
        "Results", 
        _reader.FindByEmail(email));
}

The StudentInfoController depends upon the IStudentProfileReader interface. It expects that interface to be implemented properly. If I implement a reader like below the controller would not behave as expected.

public class NoFindAllowedStudentProfileReader : IStudentProfileReader
{
    public Student Get(int id) => new Student();
    
    public IEnumerable<Student> FindByEmail(string email) => 
        throw new Exception("I don't want to do this!");

LSP has been broken. The consumer of an interface has been blindsided by something not implemented in a concrete class. The purpose of programming to an interface is to eliminate the decision making when that interface is being used.

There are going to be times where LSP has to be broken in some way. Those times should point us to other design patterns such as one of the Factory family of patterns. A Google search for "factory design patterns c#" returns a lot of results. Ideally LSP would never be violated since that would be the least risky way of doing things. The next article will talk about the Interface Segragation Principal (ISP). LSP and ISP are closely related and work well with each other. By following one it helps to follow the other.