DEV Community

Saras Growth Space
Saras Growth Space

Posted on

LLD Foundations: SOLID Principles (Part 2 — LSP, ISP, DIP)

In the previous post, we covered:

  • SRP (keep classes focused)
  • OCP (extend without modifying)

Those help you structure code well.

But even with those applied, systems can still break in subtle ways:

  • inheritance behaving unexpectedly
  • classes forced to implement irrelevant methods
  • tight coupling making changes painful

That’s where the remaining SOLID principles come in:

  • LSP (Liskov Substitution Principle)
  • ISP (Interface Segregation Principle)
  • DIP (Dependency Inversion Principle)

LSP — Liskov Substitution Principle

Subclasses should be replaceable for their base classes without breaking behavior.


The classic mistake

Bird:
- fly()

Penguin extends Bird:
- fly() → throws error
Enter fullscreen mode Exit fullscreen mode

This design assumes:

All birds can fly

But penguins cannot.

Now, anywhere Bird is used, substituting it with Penguin breaks the system.


What actually went wrong

The issue is not inheritance.

The issue is:

The base class was designed incorrectly.


Correct approach

Bird:
- eat()
- make_sound()

FlyingBird:
- fly()
Enter fullscreen mode Exit fullscreen mode

Now:

  • Sparrow → Bird + FlyingBird
  • Penguin → Bird

No invalid behavior. No surprises.


Key insight

If a subclass cannot fully support a method, the abstraction is wrong.


ISP — Interface Segregation Principle

Clients should not be forced to depend on methods they don’t use.


The common mistake

Device:
- print()
- scan()
- fax()
Enter fullscreen mode Exit fullscreen mode

Now:

  • A simple printer must implement scan and fax
  • A scanner must implement print

This leads to:

  • unused methods
  • dummy implementations
  • confusion in design

Correct approach

Split interfaces:

Printable → print()
Scannable → scan()
Faxable → fax()
Enter fullscreen mode Exit fullscreen mode

Now:

  • Printer → Printable
  • Scanner → Scannable
  • All-in-one → Printable + Scannable + Faxable

Each class implements only what it needs.


Key insight

Smaller, focused interfaces lead to cleaner and more flexible systems.


DIP — Dependency Inversion Principle

High-level modules should not depend on low-level modules. Both should depend on abstractions.


The common mistake

NotificationService → EmailSender
Enter fullscreen mode Exit fullscreen mode

This creates tight coupling.

Problems:

  • Switching to SMS requires code changes
  • Adding new channels becomes harder

Correct approach

Introduce abstraction:

Notification:
- send()

EmailNotification → send()
SMSNotification → send()
PushNotification → send()
Enter fullscreen mode Exit fullscreen mode

Now:

NotificationService:
- notify(notification)
Enter fullscreen mode Exit fullscreen mode

The service doesn’t care about implementation details.


Important design detail

Who decides which notification to send?

The caller (e.g., OrderService)

This keeps:

  • NotificationService simple
  • System extensible

Key insight

Depend on behavior (interfaces), not concrete implementations.


Bringing it all together

These three principles solve different kinds of design problems:

  • LSP → prevents broken inheritance
  • ISP → prevents bloated interfaces
  • DIP → prevents tight coupling

Combined with SRP and OCP, they give you:

A system that is stable, flexible, and easy to extend


A practical way to remember

When designing, ask:

  • Will this subclass behave correctly? (LSP)
  • Am I forcing unnecessary methods? (ISP)
  • Am I tightly coupled to implementations? (DIP)

Closing thought

Most design issues don’t show up immediately.

They appear when systems evolve.

SOLID principles help you design not just for today, but for change over time.

Top comments (0)