SOLID Principles: Writing Maintainable Object-Oriented Code
The SOLID principles constitute a set of 5 foundational design rules for object-oriented software engineering, formalized to address structural decay in large codebases. Each principle targets a distinct failure mode — from classes that grow bloated with unrelated responsibilities to modules that break when dependencies change. Understanding how these principles are structured and where they apply is essential for engineers navigating software architecture patterns, design patterns, and clean code practices in professional contexts.
Definition and scope
SOLID is an acronym coined by Robert C. Martin (also known as "Uncle Bob") and later structured by Michael Feathers. The 5 principles are: Single Responsibility (SRP), Open/Closed (OCP), Liskov Substitution (LSP), Interface Segregation (ISP), and Dependency Inversion (DIP). The IEEE Software Engineering Body of Knowledge (SWEBOK v4) classifies design principles of this kind under the Software Design knowledge area, specifically within the subsection addressing software structure and architecture.
The scope of SOLID extends across any object-oriented language or framework — Java, C#, Python, and C++ all support the structural patterns these principles describe. Their application is not language-specific but rather paradigm-specific: they apply wherever classes, interfaces, and inheritance hierarchies are used to model domain behavior.
SOLID principles are distinct from design patterns such as those catalogued in the Gang of Four text. Design patterns are reusable solutions to common structural problems; SOLID principles are evaluative criteria for determining whether a design is sound. A pattern may satisfy or violate SOLID principles depending on how it is implemented. The Software Engineering Authority reference index provides broader context for how design disciplines like these fit into the professional practice of software engineering.
How it works
Each of the 5 principles operates on a discrete axis of class or module behavior:
-
Single Responsibility Principle (SRP): A class should have exactly 1 reason to change. When a class handles both data persistence and business rule validation, a change to the database schema forces a change to the business logic file — coupling that SRP prohibits. The solution is to separate these concerns into distinct classes.
-
Open/Closed Principle (OCP): Software entities should be open for extension but closed for modification. Concretely, adding a new payment method to a billing system should not require editing the core billing class. This is achieved through abstraction — base classes or interfaces define behavior; subclasses extend it without altering the original.
-
Liskov Substitution Principle (LSP): Subtypes must be substitutable for their base types without altering the correctness of the program. If a
Rectangleclass exists and aSquaresubclass overridessetWidthin ways that break behavior valid forRectangle, LSP is violated. The canonical measure is behavioral compatibility: any function operating on the base type must operate correctly on any subtype. -
Interface Segregation Principle (ISP): Clients should not be forced to depend on interfaces they do not use. A single monolithic interface with 12 methods forces implementing classes to provide stub implementations for methods irrelevant to their purpose. ISP calls for narrow, role-specific interfaces.
-
Dependency Inversion Principle (DIP): High-level modules should not depend on low-level modules; both should depend on abstractions. A reporting module that directly instantiates a
MySQLDatabaseclass is coupled to a specific technology. Inverting that dependency — by programming to aDatabaseInterface— allows the implementation to change without touching the reporting logic.
Collectively, these 5 principles are applied during code review best practices cycles and serve as evaluation criteria in refactoring decisions where legacy class structures are decomposed.
Common scenarios
Enterprise application architecture: In enterprise-grade systems, SRP and DIP are the most frequently violated principles as systems grow. App Development Authority covers the governance frameworks and architectural requirements that shape enterprise application design, including how separation of concerns maps to team ownership boundaries in large organizations — a structural alignment that reinforces SRP at the organizational level.
Microservices decomposition: When monolithic systems are broken into microservices, each service boundary should reflect SRP applied at the service level — one bounded context, one reason to change. DIP governs how services communicate through well-defined interfaces rather than direct implementation coupling.
Test-driven development: Test-driven development depends heavily on DIP and ISP. Classes tightly coupled to concrete implementations cannot be isolated in unit tests without invoking real infrastructure. Programming to interfaces enables injection of test doubles, reducing test surface area to exactly the behavior under test.
Legacy system modernization: In legacy system modernization efforts, OCP violations are frequently the root cause of change amplification — where a single business rule change requires edits across 15 or more files because the system was never designed for extension. SOLID audits are a standard first step in modernization assessments.
Decision boundaries
The decision to apply a specific SOLID principle is not automatic — context determines which principles are most critical and when strict application produces diminishing returns.
SRP vs. cohesion: Over-application of SRP can produce excessive class fragmentation, where 40 single-method classes replace 4 coherent classes with clear internal structure. The ACM Computing Surveys literature on software modularity treats cohesion — the degree to which elements within a module belong together — as a counterbalancing force. High cohesion within a class may justify multiple methods that all serve the same conceptual purpose.
LSP in inheritance vs. composition: When LSP cannot be satisfied through inheritance without contorting behavior, the preferred resolution is composition over inheritance — a structural choice also endorsed in object-oriented programming principles. Composition sidesteps the substitutability problem by removing the inheritance relationship entirely.
DIP in small systems: In scripts, utilities, or systems below roughly 5,000 lines of code, the overhead of defining abstractions for every dependency can exceed the benefit. DIP's value is proportional to system size and the likelihood of implementation-level change. Technical debt analysis should weigh the cost of retrofitting DIP later against the overhead of introducing it prematurely.
ISP and API stability: Interface segregation is most critical when interfaces are published externally — as in public APIs or shared libraries. Internal interfaces that change in lockstep with their implementations carry lower ISP risk. API design and development standards treat interface narrowness as a stability mechanism, aligning with ISP at the contract level.
References
- IEEE Software Engineering Body of Knowledge (SWEBOK v4) — IEEE Computer Society; classifies design principles under the Software Design knowledge area
- ACM Digital Library — peer-reviewed computing research including software modularity, cohesion, and coupling literature
- IEEE Standards Association — publishes IEEE 12207 (Software Lifecycle Processes) and related software engineering standards
- ISO/IEC 25010:2023 — ISO/IEC standard for software product quality, including maintainability characteristics that SOLID principles directly address
- NIST SP 800-218 (Secure Software Development Framework) — NIST framework for secure software development practices, relevant to design-phase security decisions including dependency management