Welcome back to the second part of our journey into mastering the SOLID principles in Flutter. In the previous part, we explored:
β
 S β Single Responsibility Principle
β
 O β Open/Closed Principle
Today, weβre diving into two more important concepts thatβll help make your code more safe, scalable, and focused:
L - Liskov Substitution Principle (LSP)
π Definition:
Subtypes must be substitutable for their base types without altering the correctness of the program.
ποΈ Real-Life Analogy:
You rent a sports car, and later they replace it with a sedan.
You expect:
- It should still drive π
- Use the same controls πΉοΈ
- Not break down when you turn the key π
Thatβs LSP β if something claims to be a type, it should behave like that type without surprises.
π‘ Flutter-Specific Example:
β Wrong: Violating LSP
class Bird {
  void fly() {
    print("Flying...");
  }
}
class Ostrich extends Bird {
  @override
  void fly() {
    throw Exception("Ostriches can't fly!");
  }
}
Problem:
- Ostrich is a Bird, but replacing a Bird with an Ostrich breaks the app!
- It violates expectations of the base class.
β
 Right: Respect LSP via abstraction
abstract class Bird {}
abstract class FlyingBird extends Bird {
  void fly();
}
class Sparrow extends FlyingBird {
  @override
  void fly() {
    print("Flying high!");
  }
}
class Ostrich extends Bird {
  // Doesnβt extend FlyingBird, so no broken promises
}
Now:
- Code expecting a FlyingBird will only get actual flying birds.
- Ostrich wonβt be misused in flying-related logic.
π¦ Flutter Real Case β Widget Inheritance
Avoid this π
class CustomButton extends ElevatedButton {
  // Trying to override core ElevatedButton behavior
  // Can break consumers expecting regular ElevatedButton
}
Instead: β
class CustomButton extends StatelessWidget {
  final String label;
  final VoidCallback onPressed;
  const CustomButton({required this.label, required this.onPressed});
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: onPressed,
      child: Text(label),
    );
  }
}
Why? Youβre creating new behavior without violating the contract of built-in widgets.
π Key Takeaways:
- Donβt override methods in a way that breaks expectations.
- Subclass only when the child truly behaves like the parent.
- If behavior differs, prefer composition or separation, not inheritance.
π§ Interview One-Liner:
βLSP means I can swap any child class with its parent and the app still works correctly. In Flutter, I use it by avoiding inappropriate overrides and designing my inheritance carefully.β
I - Interface Segregation Principle (ISP)
π Definition:
Clients should not be forced to depend on interfaces they do not use.
π± Real-Life Analogy:
Imagine a food delivery app asking you to:
- Select your food β
- Enter address β
- Rate the driver β
- But also... provide restaurant inventory info? β
Why should you, the end-user, be responsible for restaurant tasks?
Similarly, in code:
- Donβt force a class to implement methods it doesnβt need.
π‘ Flutter-Specific Example:
β Wrong:
abstract class SocialFeatures {
  void post();
  void like();
  void comment();
  void blockUser();
}
class ReadOnlyUser implements SocialFeatures {
  @override
  void post() {
    throw UnimplementedError();
  }
  @override
  void like() {
    throw UnimplementedError();
  }
  @override
  void comment() {
    throw UnimplementedError();
  }
  @override
  void blockUser() {
    print("User blocked");
  }
}
Problem:
ReadOnlyUser is forced to implement methods it doesnβt need.
β
 Right:
Break interfaces into smaller ones:
abstract class CanBlock {
  void blockUser();
}
abstract class CanPost {
  void post();
  void like();
  void comment();
}
class ReadOnlyUser implements CanBlock {
  @override
  void blockUser() {
    print("User blocked");
  }
}
class ActiveUser implements CanPost, CanBlock {
  @override
  void post() => print("Posted");
  @override
  void like() => print("Liked");
  @override
  void comment() => print("Commented");
  @override
  void blockUser() => print("Blocked");
}
Now:
Each class only implements what it needs, and nothing else.
π¦ Flutter Example β Bad Form Validation
abstract class FormValidator {
  String? validateEmail();
  String? validatePhone();
  String? validatePassword();
}
If your form only has email, you're still forced to implement all three.
β
 Split Validators:
abstract class EmailValidator {
  String? validateEmail();
}
abstract class PasswordValidator {
  String? validatePassword();
}
class LoginValidator implements EmailValidator, PasswordValidator {
  @override
  String? validateEmail() => null;
  @override
  String? validatePassword() => null;
}
π Key Takeaways:
- Prefer many small, specific interfaces over one large interface.
- Avoid forcing unrelated responsibilities onto a class.
- Makes code cleaner and more flexible.
π§ Interview One-Liner:
βInterface Segregation helps me design focused contracts. In Flutter, I split large interfaces into smaller validators or roles so that classes only take what they need β nothing extra.β
β
 That wraps up Part 2 of the series!
Today, we explored:
π© Liskov Substitution Principle β Why subclasses should behave like their parents
π¦ Interface Segregation Principle β How to keep interfaces lean and focused
With real-life analogies and clean Flutter examples, you now know how to avoid broken inheritance and bloated interfaces β two of the most common clean-code traps in app development.
π¬ If this helped, Iβd love to hear your feedback, thoughts, or questions in the comments.
π Stay tuned for Part 3, where weβll wrap up the series with the final (and powerful) principle:
π« D β Dependency Inversion Principle
Letβs finish strong and make our Flutter code truly solid. π₯
 
 
              
 
    
Top comments (0)