Mastering SOLID: Real‑World Java Examples for Clean OOP Design
This article explains the five SOLID object‑oriented design principles with everyday Java scenarios, showing bad implementations, proper refactorings, practical criteria for each rule, and common pitfalls to help developers write more maintainable and extensible code.
S – Single Responsibility Principle (SRP)
One sentence: a class should do only one thing, just like a job that handles a single type of work; it must not be forced to perform unrelated tasks.
Bad example: a User class that simultaneously saves to a database, validates phone numbers, sends registration SMS, and generates user reports. Changing the SMS logic could break database saving, and changing the report logic could affect registration.
Good practice: split responsibilities into separate classes: User – only stores user data (entity). UserValidator – validates phone numbers and parameters. UserDbService – handles CRUD operations. SmsService – sends SMS messages. UserReportService – generates reports.
Criterion: if a class can be motivated to change for two completely different reasons, it violates SRP and should be split.
O – Open/Closed Principle (OCP)
One sentence: software entities should be open for extension but closed for modification; new functionality is added by introducing new code rather than altering existing, stable code.
Bad example: a PayService class that originally supports WeChat payment; to add Alipay, developers insert an if‑else block inside the same class, risking bugs in the already stable code.
Good practice: define a Pay interface; implement WeChatPay and Alipay as separate classes. Adding a new payment method (e.g., Douyin Pay) only requires a new implementation without touching existing ones.
Analogy: a wall socket lets you plug in new appliances by adding a new plug, not by rewiring the socket.
L – Liskov Substitution Principle (LSP)
One sentence: objects of a subclass must be replaceable for objects of the superclass without altering the desirable properties of the program.
Bad example: a base class Bird defines fly(). A Penguin subclass overrides fly() to throw an exception because penguins cannot fly. Code that expects a Bird now crashes when a Penguin is substituted.
Correct approach: if a subclass cannot faithfully implement a behavior, use composition instead of inheritance. Extract a Flyable interface; only flying birds implement it, while Penguin does not inherit the fly capability.
Analogy: a replacement battery must work like the original; a incompatible battery that shuts the device down violates LSP.
I – Interface Segregation Principle (ISP)
One sentence: clients should not be forced to depend on interfaces they do not use; interfaces should be small and role‑specific.
Bad example: a monolithic Worker interface that includes methods for coding, testing, product research, and deployment. A Programmer class that implements Worker must provide empty or exception‑throwing implementations for testing and deployment, which it never needs.
Good practice: split Worker into several fine‑grained interfaces such as Coder, Tester, and Ops. A programmer implements only Coder and writes the methods it actually requires.
Analogy: a universal remote with buttons for TV, air‑conditioner, and washing machine forces you to handle functions you never use; separate remotes avoid this.
D – Dependency Inversion Principle (DIP)
One sentence: high‑level modules should not depend on low‑level concrete implementations; both should depend on abstractions (interfaces or abstract classes).
Bad example: OrderService directly creates a new MysqlOrderDao(), tying the business logic to MySQL. Switching to Oracle would require changing every place that instantiates the concrete DAO.
Good practice:
Define an OrderDao interface.
Provide MysqlOrderDao and OracleOrderDao implementations.
Let OrderService depend only on OrderDao and obtain the concrete implementation via dependency injection (e.g., Spring).
Now changing the database requires only swapping the injected implementation; the service code remains untouched. Analogy: a manager (high‑level) works with a job description (interface) rather than a specific employee; any employee who fulfills the description can be hired without changing the manager’s workflow.
Overall Summary of SOLID
1. Single Responsibility: a class should have one reason to change. 2. Open/Closed: extend behavior by adding new code, minimize modifications to stable code. 3. Liskov Substitution: subclasses must honor the contracts of their superclasses; otherwise prefer composition. 4. Interface Segregation: keep interfaces small and focused. 5. Dependency Inversion: program to abstractions, not concrete implementations.
Common Misconceptions
• Not every tiny utility class needs strict adherence; over‑engineering can make code harder to read. • SOLID aims to accommodate future changes; if a feature is guaranteed never to evolve, applying all five rules may be unnecessary. • The principles work together: SRP provides the foundation, OCP sets the goal, and LSP, ISP, and DIP are the mechanisms to achieve it.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
CTO Full-Stack Academy
15 years of IT industry experience, sharing practical insights on pre-sales, product design, architecture, technology development, software testing, project management, IT consulting, and operations management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
