Exploring SOLID Principles in .NET Core Applications — part 3
New to .NET Core development? Let’s delve into SOLID principles to enhance your coding skills. This is part three of a series of five see the previous part here
Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) is one of the SOLID principles, and it focuses on the proper usage of inheritance in object-oriented programming. It states that objects of derived classes should be substitutable for objects of their base classes without affecting the correctness of the program. In simpler terms, if a class is derived from another class, it should be able to be used interchangeably with its base class without causing unexpected behaviour or violating the contract established by the base class. Here’s an explanation and code samples that first violate LSP and then adhere to it.
Explanation:
Violating the LSP can lead to unexpected and incorrect behavior when substituting derived class objects for base class objects.
Adhering to LSP means that derived classes should extend the behavior of base classes without altering their fundamental contract (methods, properties, behavior).
Inheritance should not break the substitutability of derived classes for their base classes.
Let’s start with a code sample that violates the LSP:
// Violating LSP: Derived class behavior is not substitutable for the base class.
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
}
public class AdminUser : User
{
public void GrantAdminPrivileges()
{
// Logic to grant admin privileges
Console.WriteLine($"{Username} is now an admin user.");
}
}
public class RegularUser : User
{
public void PerformRegularTask()
{
// Logic for a regular user task
Console.WriteLine($"{Username} is performing a regular user task.");
}
}
In this code, we have a User
base class, and two derived classes: AdminUser
and RegularUser
. The violation of LSP occurs because AdminUser
and RegularUser
have added behaviour (GrantAdminPrivileges
and PerformRegularTask
, respectively) that is not substitutable for the base class User
. This means you cannot use an AdminUser
or RegularUser
object interchangeably with a User
object without encountering issues.
Let’s refactor the code to adhere to LSP:
// Adhering to LSP: Derived classes extend behavior without breaking substitutability.
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
}
public abstract class UserType
{
public abstract void PerformUserSpecificTask(User user);
}
public class AdminUser : UserType
{
public override void PerformUserSpecificTask(User user)
{
// Logic to grant admin privileges
Console.WriteLine($"{user.Username} is now an admin user.");
}
}
public class RegularUser : UserType
{
public override void PerformUserSpecificTask(User user)
{
// Logic for a regular user task
Console.WriteLine($"{user.Username} is performing a regular user task.");
}
}
In this refactored code:
- We’ve introduced an
UserType
base class that acts as an abstraction for user-specific tasks. - Both
AdminUser
andRegularUser
classes now inherit fromUserType
and provide their own implementations for thePerformUserSpecificTask
method, which accepts aUser
object as a parameter.
This adheres to the Liskov Substitution Principle because AdminUser
and RegularUser
can be used interchangeably with the base class UserType
without breaking the substitutability. You can now call the PerformUserSpecificTask
method on any UserType
object and it will execute the appropriate behaviour for that user type without unexpected behaviour or violations of the contract.