Understanding Liskov Substitution Principle with Real Construction Examples

Understanding Liskov Substitution Principle with Real Construction Examples

The Liskov Substitution Principle (LSP) ensures that a subclass can stand in for its parent class without introducing errors or unexpected behavior. In the context of construction project management—such as invoice processing, payments, and expense tracking—LSP helps ensure your system remains stable, extensible, and reliable.

LSP Definition: Objects of a subclass should be replaceable for objects of the base class without changing the correctness of the program.

 

🔑 Key Rules of LSP

  • Behavior Preservation: Subclass behavior must match expectations of the base class.
  • No Contradictions: Subclasses should not override methods with conflicting behavior.
  • No Side Effects: Substituting subclass must not break logic.
  • Contract Adherence: Preconditions must not be tightened; postconditions must be honored.
  • Safe Polymorphism: Derived class can be used wherever base class is used.

🏗 Example 1: Behavior Preservation

class PaymentMethod
{
    public virtual string ProcessPayment(decimal amount) => $"Paid {amount}";
}

class BankTransfer : PaymentMethod
{
    public override string ProcessPayment(decimal amount) => $"Bank Transfer: {amount} successful";
}

void PayContractor(PaymentMethod method, decimal amount)
{
    Console.WriteLine(method.ProcessPayment(amount));
}

✅ Result: No surprise behavior. BankTransfer behaves like a PaymentMethod.

🚫 Example 2: Contradiction Violation

class CreditPayment : PaymentMethod
{
    public override string ProcessPayment(decimal amount)
    {
        throw new InvalidOperationException("Credit payment not allowed");
    }
}
⚠️ This breaks LSP because the subclass throws an exception unexpectedly.
Fix: Refactor using interfaces for incompatible behavior:
interface IPaymentMethod
{
    string ProcessPayment(decimal amount);
}

class CreditPayment
{
    public string RequestApproval(decimal amount) => $"Credit approval needed for {amount}";
}

🚫 Example 3: Side Effects in Subclass

class Expense
{
    public virtual decimal Amount { get; set; }
}

class MaterialExpense : Expense
{
    public override decimal Amount
    {
        get => base.Amount;
        set
        {
            base.Amount = value;
            SendNotification(); // Side effect!
        }
    }

    private void SendNotification()
    {
        Console.WriteLine("Email sent for material expense.");
    }
}
⚠️ Side effects like notifications in property setters violate LSP.
Fix: Use explicit methods to separate concerns.
class MaterialExpense : Expense
{
    public void SetAmountAndNotify(decimal amount)
    {
        Amount = amount;
        SendNotification();
    }

    private void SendNotification()
    {
        Console.WriteLine("Email sent for material expense.");
    }
}

🚫 Example 4: Contract Violation in Invoice System

class Invoice
{
    public virtual void Submit(decimal amount)
    {
        if (amount <= 0)
            throw new ArgumentException("Amount must be positive");

        Console.WriteLine("Invoice submitted");
    }
}

class OnlineInvoice : Invoice
{
    public override void Submit(decimal amount)
    {
        if (amount < 1000)
            throw new ArgumentException("Online invoice must be ≥ 1000");

        base.Submit(amount);
    }
}
⚠️ OnlineInvoice introduces stricter rules than the base class. This breaks LSP.
Fix: Enforce such rules externally, not in subclasses.
class OnlineInvoice : Invoice
{
    public override void Submit(decimal amount)
    {
        base.Submit(amount);
        Console.WriteLine("Online confirmation sent");
    }
}

✅ Example 5: Safe Polymorphism with Expenses

class Expense
{
    public virtual string GetDetails() => "Generic expense";
}

class LabourExpense : Expense
{
    public override string GetDetails() => "Labour charges";
}

class TransportExpense : Expense
{
    public override string GetDetails() => "Transport of materials";
}

void PrintExpenseDetails(Expense expense)
{
    Console.WriteLine(expense.GetDetails());
}

✅ Result: You can pass any type of Expense to the method, and behavior remains consistent.

🔚 Final Thoughts

The Liskov Substitution Principle ensures that your construction system remains robust even as complexity increases. Violating LSP often leads to bugs, tight coupling, and unexpected failures when working with polymorphism.

✅ Ask yourself: “Can I replace the base class with this subclass without any surprises?” If not, refactor!

🛠 Benefits of LSP

  • Prevents unpredictable subclass behavior
  • Makes code easier to extend and test
  • Promotes cleaner design with clear responsibilities

📘 Want More?

Let me know!

Comments

Popular posts from this blog

Debouncing & Throttling in RxJS: Optimizing API Calls and User Interactions

Promises in Angular

Comprehensive Guide to C# and .NET Core OOP Concepts and Language Features