Tutorials SOLID Design Principles Tutorial
Liskov Substitution Principle — Complete Guide
Liskov Substitution Principle — Complete Guide: free step-by-step lesson with examples, common mistakes, and interview tips — part of SOLID Design Principles Tutorial on Toolliyo Academy.
On this page
Introduction
Liskov Substitution Principle — Complete Guide is essential for .NET developers building ShopNest Enterprise Clean Architecture Platform — Toolliyo's 100-article SOLID master path covering SRP, OCP, LSP, ISP, DIP, refactoring, Clean Architecture, and ten enterprise projects. Every article includes minimum two detailed real-world examples with bad code before good code.
In Indian delivery projects (TCS, Infosys, Wipro), interviewers expect liskov substitution principle with HDFC banking SRP fixes, Flipkart OCP payment strategies, and legacy refactoring stories — not toy animal demos. This article delivers production depth on Reporting.
After this article you will
- Explain Liskov Substitution Principle in plain English and in SOLID / maintainable OOP terms
- Apply liskov substitution principle to ShopNest Clean Architecture (Reporting module)
- Compare bad architecture vs production-ready SOLID refactor
- Answer fresher and senior SOLID / clean architecture interview questions confidently
- Connect this lesson to Article 32 and the 100-article SOLID roadmap
Prerequisites
- Software: .NET 10 SDK, VS 2022 or VS Code, xUnit + Moq NuGet packages
- Knowledge: C# basics
- Previous: Article 30 — OCP Interview Questions — Complete Guide
- Time: 24 min reading + 30–45 min hands-on refactor
Concept deep-dive
Level 1 — Analogy
LSP is like substituting any licensed driver — the car must behave predictably; a toy pedal car cannot replace a real car in a race.
Level 2 — Technical
Liskov Substitution Principle guards LSP — subtypes must honor base contracts; prefer composition over inheritance that throws NotImplementedException.
Level 3 — Clean Architecture view
[API Controller / Worker]
▼
[Application Layer — Handlers / Services]
▼ depends on abstractions
[Domain Layer — Entities / Value Objects]
▼ implemented by
[Infrastructure — EF Core, Email, Payment Gateways]
▼
[DI Container — Program.cs registrations]
▼
[xUnit + Moq — isolated unit tests per principle]
Common misconceptions
❌ MYTH: SOLID is only for senior architects on huge systems.
✅ TRUTH: ShopNest applies SOLID from day one — even small modules benefit when the team will grow beyond one developer.
❌ MYTH: SOLID means creating an interface for everything.
✅ TRUTH: Apply abstractions when you have multiple implementations or need test doubles — not prematurely.
❌ MYTH: Refactoring to SOLID always slows delivery.
✅ TRUTH: Short-term cost pays back in faster testing, fewer merge conflicts, and safer changes within 2–3 sprints.
Project structure
ShopNest.CleanArchitecture/
├── ShopNest.Domain/ ← Entities, value objects (no dependencies)
├── ShopNest.Application/ ← Handlers, interfaces, DTOs
├── ShopNest.Infrastructure/ ← EF Core, email, payment gateways
├── ShopNest.Api/ ← Controllers, Program.cs DI
└── ShopNest.Tests/ ← xUnit + Moq per module (Reporting)
Hands-on implementation — Reporting
Apply Liskov Substitution Principle in ShopNest Clean Architecture for Reporting: identify violation, extract interface, refactor with DI, and verify with xUnit + Moq.
- Open the ShopNest module (Orders, Payments, etc.) and locate the god class or violation.
- Extract a focused interface with one responsibility (SRP) or strategy (OCP).
- Register implementations in Program.cs with constructor DI.
- Write xUnit tests with Moq for the new abstraction.
- Run dotnet test and compare cyclomatic complexity before/after refactor.
Anti-pattern (god class, if/else chains, concrete new())
// ❌ BAD — god class violates SRP, tight coupling, untestable
public class OrderService {
public void PlaceOrder(Order o) {
Validate(o);
_context.Orders.Add(o);
_context.SaveChanges();
SendEmail(o.CustomerEmail);
GenerateInvoicePdf(o);
}
}
Production-style SOLID refactor
// ✅ CORRECT — Liskov Substitution Principle on ShopNest (Reporting) — SOLID applied
public sealed class PlaceOrderHandler(
IOrderRepository repo,
INotificationService notify) : IRequestHandler<PlaceOrderCommand, Result>
{
public async Task<Result> Handle(PlaceOrderCommand cmd, CancellationToken ct) {
var order = Order.Create(cmd.CustomerId, cmd.Items);
await repo.AddAsync(order, ct);
await notify.OrderPlacedAsync(order, ct);
return Result.Success(order.Id);
}
}
Complete example
// LSP violation: Square extends Rectangle but breaks SetWidth
// Fix: IShape with Area() — no broken inheritance
The problem before SOLID
Without SOLID, ShopNest teams hit: tight coupling, god classes, untestable controllers, merge conflicts, and fear of refactoring. Indian IT projects (TCS, Infosys, Wipro) lose sprints when legacy code has no clear boundaries.
- Tight coupling — change SMS provider, break ledger posting
- Testing difficulty — cannot mock database from controller
- Scalability — monolith teams block each other
- Bug-prone — one class, five reasons to change
Real-World Example 1 — TCS ERP — LSP with Employee Hierarchy
MANDATORY enterprise scenario (Enterprise ERP): Liskov Substitution Principle applied in ShopNest Clean Architecture Reporting.
Business problem
ContractEmployee inherited FullTimeEmployee but threw NotImplementedException on ApplyBonus(). LSP violation broke payroll batch. Fixed with IEmployee interface and separate bonus policies.
Before SOLID — bad design
public class ContractEmployee : FullTimeEmployee {
public override void ApplyBonus(decimal amount) =>
throw new NotImplementedException("Contractors get no bonus");
}
After SOLID — production design
public interface IEmployee {
decimal CalculatePay(PayPeriod period);
}
public class FullTimeEmployee : IEmployee { /* bonus eligible */ }
public class ContractEmployee : IEmployee { /* no bonus — no fake override */ }
Outcome
Payroll batch stopped throwing at 2 AM; HR can add new employment types without breaking existing workers.
Real-World Example 2 — Freshworks CRM — ISP on Fat ICustomerService
MANDATORY enterprise scenario (SaaS CRM): Liskov Substitution Principle applied in ShopNest Clean Architecture Reporting.
Business problem
ICustomerService had 18 methods; read-only report API was forced to implement DeleteCustomer and MergeDuplicates. ISP split into ICustomerReader, ICustomerWriter, ICustomerAdmin.
Before SOLID — bad design
public interface ICustomerService {
Customer Get(int id); void Create(Customer c); void Delete(int id);
void Merge(int a, int b); byte[] ExportPdf(); /* 13 more... */
}
After SOLID — production design
public interface ICustomerReader { Customer Get(int id); IReadOnlyList<Customer> Search(string q); }
public interface ICustomerWriter { void Create(Customer c); void Update(Customer c); }
public interface ICustomerAdmin { void Delete(int id); void Merge(int a, int b); }
Outcome
Report microservice depends only on ICustomerReader — security audit passed least-privilege review.
SOLID in ASP.NET Core — Liskov Substitution Principle
Register abstractions in Program.cs as Scoped. Keep controllers thin — delegate to MediatR handlers or application services. ShopNest Clean Architecture: Domain → Application → Infrastructure → Api.
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(PlaceOrderHandler).Assembly));
SOLID and design patterns
SRP enables focused classes; OCP pairs with Strategy and Factory; LSP guards inheritance; ISP splits fat interfaces; DIP powers DI and Repository pattern. SOLID is the foundation — patterns are the tools.
Unit testing with SOLID
var mock = new Mock<IOrderRepository>();
mock.Setup(r => r.GetAsync(1, default)).ReturnsAsync(new Order(1, 100m));
var handler = new GetOrderHandler(mock.Object);
var result = await handler.Handle(new GetOrderQuery(1), default);
Assert.Equal(100m, result.Total);
Pattern recognition
God class → SRP split. if/else feature growth → OCP Strategy. Broken subclass → LSP composition. Fat interface → ISP split. new Concrete() → DIP + DI. Legacy monolith → strangler fig refactor.
Common errors & fixes
- God classes with 10+ responsibilities (SRP violation) — Extract focused services — one reason to change per class.
- Adding if/else chains for every new feature (OCP violation) — Use Strategy or Factory; extend via new classes, not edits.
- Subclass throws NotImplementedException (LSP violation) — Prefer composition and role-specific interfaces over broken inheritance.
- Controllers new-ing concrete repositories (DIP violation) — Inject interfaces via constructor DI in ASP.NET Core.
Best practices
- 🟢 One reason to change per class (SRP)
- 🟢 Extend via new classes, not edits (OCP)
- 🟡 Introduce interfaces when you need test doubles or multiple implementations
- 🟡 Keep controllers thin — delegate to handlers/services
- 🔴 Never skip characterization tests before legacy refactors
- 🔴 Register all abstractions in Program.cs — avoid service locator anti-pattern
Interview questions
Fresher level
Q1: What is Liskov Substitution Principle and which SOLID letter does it relate to?
A: Liskov Substitution Principle maps to LSP on ShopNest Reporting. Explain the principle in one sentence, then give a before/after code example.
Q2: Explain SRP with a real example.
A: Split god classes — TransferService becomes validator, ledger, fraud checker, notifier. One reason to change per class.
Q3: OCP vs inheritance — when is inheritance wrong?
A: When subclasses break base behavior (LSP). Prefer Strategy/Factory for extension without modification.
Mid / senior level
Q4: How does DIP relate to ASP.NET Core DI?
A: Program.cs registers interfaces to implementations; controllers/handlers depend on abstractions only.
Q5: When should you NOT apply SOLID?
A: Throwaway prototypes, scripts, or 50-line utilities — apply when the module will grow or be team-owned.
Q6: How do you refactor legacy code safely?
A: Characterization tests first, extract interface, inject via DI, migrate callers incrementally (strangler fig).
Coding round
Refactor a god-class OrderService into SRP-compliant services with DI registration and one xUnit test using Moq.
public sealed class PlaceOrderHandler(IOrderRepository repo, INotifier notify)
{
public async Task Handle(PlaceOrderCommand cmd, CancellationToken ct) {
var order = Order.Create(cmd.Items);
await repo.AddAsync(order, ct);
await notify.SendAsync(order, ct);
}
}
Summary & next steps
- Article 31: Liskov Substitution Principle — Complete Guide
- Module: Module 4: Liskov Substitution Principle (LSP) · Level: INTERMEDIATE · Principle: LSP
- Applied to ShopNest Clean Architecture — Reporting
Previous: OCP Interview Questions — Complete Guide
Next: LSP Bad Example — Complete Guide
Practice: Refactor one small class using today's principle — commit with refactor(solid): article-31.
FAQ
Q1: What is Liskov Substitution Principle?
Liskov Substitution Principle helps ShopNest Clean Architecture implement the Reporting module using LSP and C# best practices.
Q2: Do I need design patterns before SOLID?
No — SOLID is foundational. Patterns (Strategy, Factory, Repository) are tools that implement SOLID.
Q3: Is SOLID asked in Indian IT interviews?
Yes — SRP, OCP, and DIP appear in TCS, Infosys, Wipro lateral hires; senior roles ask refactoring war stories.
Q4: Which .NET version?
Examples target .NET 10 with C# 14 and ASP.NET Core DI.
Q5: How does this fit ShopNest Clean Architecture?
Article 31 strengthens Reporting with LSP. By Article 100 you have a portfolio-ready enterprise architecture.
Sign in to ask a question or upvote helpful answers.
No questions yet — be the first to ask!