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.