Hello! Welcome back to our module on SOLID design principles.
In the previous lesson, we established a strong foundation with the Single Responsibility Principle (SRP) and the Open/Closed Principle (OCP). We learned the importance of giving a class a single reason to change (SRP) and designing our code to be extensible without needing modification (OCP), primarily through the use of abstractions like interfaces.
This lesson continues our journey toward an "architectural mindset" by focusing on how to design those abstractions correctly. We'll explore two more crucial principles: the Liskov Substitution Principle (LSP) and the Interface Segregation Principle (ISP). By the end of this lesson, you will be able to explain what these principles are and, more importantly, apply them to redesign a class hierarchy to be more robust and maintainable.
1. Liskov Substitution Principle (LSP)
The Liskov Substitution Principle is a powerful guideline for ensuring your inheritance structures are semantically correct. It was formulated by Barbara Liskov and states:
Subtypes must be substitutable for their base types without altering the correctness of the program.
In simpler terms, if a function expects an object of a base class, it should be able to work with an object of any of its derived classes without causing errors or unexpected behavior. If you find yourself writing if (object is TypeA) checks before using a derived class, you are likely violating LSP.
A violation often occurs when a subclass changes the behavior of a method it inherits in a way that the parent class contract did not anticipateโfor example, by throwing a new exception or doing nothing when something was expected to happen.
To see this principle in action, let's watch a video that covers two classic examples.
Liskov Substitution Principle Explained - SOLID Design Principles
This short video from Web Dev Simplified provides a clear definition of LSP and demonstrates violations using the well-known Rectangle/Square and Bird/Penguin problems.
Watch from the beginning to 06:50. Pay close attention to why the substitutions fail: Definition (00:37 - 01:28): Get a clear, concise explanation of the principle. Rectangle/Square Problem (01:28 - 03:55): Notice how the Square subclass, by overriding setWidth and setHeight, breaks the expectations of a function designed to work with a Rectangle. Bird/Penguin Problem (05:09 - 06:50): See how a Penguin that cannot fly breaks the contract of its Bird parent class.
The core takeaway is that inheritance should represent a true "is-a" relationship not just in structure, but in behavior. A Square is a Rectangle mathematically, but from a behavioral standpoint in that code example, it wasn't a substitutable Rectangle.

Test your understanding!
In the Rectangle/Square example, the Square class inherits from Rectangle. The increaseRectangleWidth function takes a Rectangle object, sets its width to width + 1, and then expects the height to remain unchanged. Why does passing a Square object to this function violate LSP?
Show answer
It violates LSP because the Square subclass changes the behavior of the setWidth method in a way that the function does not expect. When setWidth is called on a Square, it also changes the height to maintain the "squareness" property. The calling function, which was designed for a Rectangle, assumes that setting the width does not affect the height. This behavioral change breaks the program's correctness, thus violating LSP.
Redesigning a Hierarchy with LSP
Let's look at a more complex, business-oriented scenario. This will show how LSP guides us to a better design using interfaces and abstract classes, a common task when building enterprise systems.
Design Patterns: Liskov Substitution Principle Explained Practically in C# (The L in SOLID)
In this video, Tim Corey demonstrates a more realistic LSP violation with an Employee/Manager/CEO hierarchy and walks through a complete refactoring. The example is in C#, but the architectural concepts are universal.
Focus on the architectural transformation: The Problem (00:00 - 06:11): Understand why a CEO class inheriting from Employee is problematic. The AssignManager method, valid for an Employee, throws an exception for a CEO, violating LSP. The Redesign (17:21 - 27:43): This is the key section. Observe how the hierarchy is redesigned by introducing several abstractions: IEmployee, IManager, IManaged interfaces are created to define specific roles/capabilities. A BaseEmployee abstract class is created to hold common properties and methods. The concrete classes (Employee, Manager, CEO) then implement the appropriate interfaces and inherit from the base class. The Result (27:43 - 32:02): See how the refactored code now adheres to LSP, allowing different employee types to be substituted correctly without causing runtime errors.
This refactoring is a perfect example of moving to an architectural mindset. Instead of patching the problem with a conditional check, the entire class structure was re-evaluated and rebuilt around well-defined contracts (interfaces), resulting in a system that is both correct and flexible.
2. Interface Segregation Principle (ISP)
The Interface Segregation Principle is about keeping interfaces lean and focused. It states:
Clients should not be forced to depend on interfaces they do not use.
This principle tackles the problem of "fat interfaces"โlarge interfaces that contain methods for many different responsibilities. When a class implements such an interface, it is often forced to provide empty or exception-throwing implementations for methods it doesn't need. This is a sign of poor cohesion and a violation of ISP.
The solution is to break down fat interfaces into smaller, more specific ones based on the roles clients play.
Understanding SOLID Principles: Interface Segregation Principle (ISP)
Let's read a clear explanation of ISP with a practical example. This article shows how a single, large IDevice interface can be problematic and how to solve it by splitting it into smaller, role-based interfaces.
Read the entire section on the 'Interface Segregation Principle (ISP)'. Pay attention to how the IDevice interface forces a Scanner class to implement a PrintDocument method it cannot support. The solution demonstrates how to create smaller interfaces like IStartable and IPrintable, allowing classes to implement only the functionalities they truly possess.
This approach leads to a more modular system where classes only know about the methods that are relevant to them.

Test your understanding!
Imagine you are designing a system for managing repository actions in a version control system like Git. You start with a single IRepositoryActions interface:
interface IRepositoryActions {
commit(message: string): void;
push(): void;
createPullRequest(title: string): void;
approvePullRequest(id: number): void;
mergePullRequest(id: number): void;
}
A Developer class might use all these methods. However, you also have a CI_Bot that only needs to commit and push code. Why does this design violate ISP for the CI_Bot client, and how would you refactor it?
Show answer
This design violates ISP because the CI_Bot client is forced to depend on methods it doesn't use (createPullRequest, approvePullRequest, mergePullRequest). If we were to create a CIBot class implementing IRepositoryActions, it would need to provide empty or exception-throwing implementations for three methods.
To fix this, we should segregate the interface by role:
ICommitterinterface withcommit()andpush().ICodeReviewerinterface withcreatePullRequest(),approvePullRequest(), andmergePullRequest().
The CI_Bot class would then only need to implement ICommitter. The Developer class could implement both ICommitter and ICodeReviewer. This way, clients only depend on the methods they actually need.
Conclusion
In this lesson, we've added two powerful principles to our design toolkit that guide us in creating high-quality abstractions.
Key Takeaways:
- Liskov Substitution Principle (LSP): Ensures that inheritance hierarchies are behaviorally sound. A subclass must be truly substitutable for its superclass without breaking the program. This often means preferring composition or more granular abstractions over incorrect inheritance.
- Interface Segregation Principle (ISP): Pushes us to create small, cohesive interfaces instead of large, "fat" ones. This prevents clients from depending on methods they don't use and makes the system more modular and decoupled.
- Redesigning Hierarchies: We saw through practical examples how applying LSP and ISP leads to redesigning class hierarchies by breaking down classes and interfaces into smaller, more focused units that better represent the roles and capabilities within the system.
Preview of the Next Lesson:
We are now ready to tackle the final SOLID principle: the Dependency Inversion Principle (DIP). This principle addresses how high-level and low-level modules should interact. It will tie together everything we've learned about abstractions and help us build truly decoupled, flexible, and pluggable software architectures.