Skip to main content
Clean Code Principles – Complete Beginner to Advanced Guide
CHAPTER 09 Intermediate

SOLID Principles for Clean Architecture

Updated: May 16, 2026
35 min read

# CHAPTER 9

SOLID Principles for Clean Architecture

1. Introduction

If DRY, KISS, and YAGNI are the street-smarts of programming, the SOLID principles are the academic foundation of enterprise architecture. Introduced by Robert C. Martin, SOLID is an acronym representing five design principles intended to make software designs more understandable, flexible, and maintainable. When code is tightly coupled, a single change in one file breaks ten other files. SOLID principles act as the architectural blueprint to decouple your code, allowing a massive system to scale infinitely without collapsing under its own weight. In this chapter, we will dissect each of the five principles, translating dense academic theory into practical, real-world engineering.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Define and apply all five SOLID principles.
  • Understand how SRP prevents "God Classes."
  • Utilize the Open/Closed Principle (OCP) to extend features without editing old code.
  • Ensure polymorphism safety using Liskov Substitution (LSP).
  • Decouple systems using Interface Segregation (ISP) and Dependency Inversion (DIP).

3. S - Single Responsibility Principle (SRP)

*A class should have one, and only one, reason to change.*
  • The Problem: A User class holds user data, formats the data into HTML, and saves the data to the SQL database. If the HTML styling changes, the class changes. If the database schema changes, the class changes. This is a fragile "God Class."
  • The Solution: Split it up.
  1. 1. User (Holds data).
  1. 2. UserView (Renders HTML).
  1. 3. UserRepository (Saves to SQL).
Now, each class has only *one* reason to change.

4. O - Open/Closed Principle (OCP)

*Software entities should be open for extension, but closed for modification.*
  • The Problem: You have a DiscountCalculator that checks if ($type == 'VIP') and else if ($type == 'HOLIDAY'). Tomorrow, marketing adds a 'SUMMER' discount. You have to open this existing file and modify the core logic, risking breaking the VIP logic.
  • The Solution: Use polymorphism (Interfaces). Create a DiscountInterface. Create separate classes: VipDiscount, HolidayDiscount, SummerDiscount. The DiscountCalculator accepts the interface. To add a new discount, you just write a *new* class. The old classes are "closed" to modification, but the system is "open" to extension.

5. L - Liskov Substitution Principle (LSP)

*Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application.*
  • The Problem: You have a Bird class with a fly() method. You create a subclass Penguin extends Bird. If another part of the system loops through an array of Birds and calls fly(), the Penguin will throw a fatal error.
  • The Solution: A Penguin is not a flying bird. Fix the abstraction. Have a generic Bird class, and an interface IFlyable. Only birds that actually fly implement IFlyable.

6. I - Interface Segregation Principle (ISP)

*No client should be forced to depend on methods it does not use.*
  • The Problem: You create an IWorker interface with work() and eat() methods. A Human implements both. A Robot implements it, but robots don't eat. The Robot class is forced to have a useless, empty eat() method.
  • The Solution: Segregate (split) the massive interface into smaller, specific ones. Create IWorkable and IFeedable. The Human implements both; the Robot only implements IWorkable.

7. D - Dependency Inversion Principle (DIP)

*High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).*
  • The Problem: A PaymentProcessor class creates a new StripeAPI() directly inside its constructor. The processor is tightly coupled to Stripe. If the business switches to PayPal, you have to rewrite the PaymentProcessor.
  • The Solution: The PaymentProcessor should ask for a generic IPaymentGateway interface in its constructor. At runtime, you inject either the StripeAdapter or the PayPalAdapter. The high-level logic now has zero dependency on the low-level API details. (This is often achieved via Dependency Injection).

8. Diagrams/Visual Suggestions

*Dependency Inversion Flow*
txt
12345678
[ BAD (Tightly Coupled) ]
OrderService ---> MySQLDatabase
(If we switch to MongoDB, OrderService breaks).

[ CLEAN (Dependency Inversion) ]
OrderService ---> [ IDatabase Interface ] <--- MySQLAdapter
                                          <--- MongoAdapter
(OrderService only knows about the Interface. Adapters can be swapped safely).

9. Best Practices

  • Design for Change: SOLID principles are not just academic theories; they are a direct response to the reality that business requirements change constantly. Applying OCP and DIP ensures that when a manager asks for a change, you write *new* files instead of surgically altering *old*, working files.

10. Common Mistakes

  • Over-Engineering (Violating KISS): Do not apply SOLID principles preemptively to a 10-line script. If an application is tiny and will never change, massive interfaces and dependency injection containers are a waste of time. Apply SOLID when the codebase begins to grow and feel painful to maintain.

11. Practice Exercises

  1. 1. Provide a real-world example of how violating the Single Responsibility Principle (SRP) makes code harder to test.
  1. 2. Explain the Liskov Substitution Principle (LSP) using a Square and Rectangle example.

12. MCQs with Answers

Question 1

A developer needs to add a new "Bitcoin" payment method to an app. Instead of modifying the massive PaymentManager class, they create a new BitcoinProcessor class that implements an existing PaymentInterface. Which SOLID principle did they just follow?

Question 2

What is the primary architectural goal of the Dependency Inversion Principle (DIP)?

13. Interview Questions

  • Q: Explain the Single Responsibility Principle (SRP). How do you know when a class has taken on too many responsibilities? (Hint: Explain the concept of "Reasons to change").
  • Q: Walk me through the Interface Segregation Principle (ISP). Why are "fat interfaces" dangerous for clients that consume them?
  • Q: You are building an email notification system. How would you use the Dependency Inversion Principle (DIP) to ensure the system can switch between SendGrid and Mailchimp without rewriting the core business logic?

14. FAQs

Q: Do I have to follow all 5 principles all the time? A: No. Software engineering is about trade-offs. Strictly adhering to all 5 principles in every tiny script creates a highly abstracted, confusing mess. Use them as guidelines for the core architectural pillars of large applications.

15. Summary

In Chapter 9, we elevated our engineering skills from writing procedural scripts to architecting enterprise systems. We explored the SOLID principles: protecting classes with Single Responsibility, making systems extensible via Open/Closed, ensuring polymorphic safety with Liskov Substitution, stripping away bloat with Interface Segregation, and decoupling entire architectures via Dependency Inversion. These principles are the defining difference between a codebase that collapses under the weight of "Spaghetti Code" and one that scales gracefully for decades.

16. Next Chapter Recommendation

We know the architectural rules. Now let's apply them to how we structure our objects and data. Proceed to Chapter 10: Object-Oriented Clean Code.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·