Difficulty: Intermediate
Explain the Single Responsibility Principle and the Open/Closed Principle with examples.
SOLID is a set of five design principles popularised by Robert C. Martin, and these two are the first and the most immediately practical. Let's take them one at a time with the mindset of "what problem does this prevent?".
The Single Responsibility Principle says a class should have only one reason to change. That does not mean one method. It means one axis of change, one stakeholder whose requirements would force you to edit the class. Imagine an Invoice class that calculates totals, formats itself as PDF, and saves itself to the database. The finance team changes tax rules, the design team changes the layout, and the DBA moves to a new schema; three unrelated groups all force edits to one file, and every change risks breaking the other two functions. The fix is to split it: Invoice holds the data and domain calculations, InvoicePrinter handles presentation, and InvoiceRepository handles persistence. Each becomes small, easy to test, and easy to reuse. SRP is what gives you high cohesion.
Do not overdo it, though. Splitting a class into ten trivial pieces just to say you followed SRP creates needless indirection. Ask "who would ask for this to change?" and group things that change together.
The Open/Closed Principle says software entities should be open for extension but closed for modification. When a new requirement arrives, you should be able to add new code rather than edit existing, tested code. The classic violation is a long if-else or switch on type. Suppose an AreaCalculator has if (shape is Circle) ... else if (shape is Rect) ... . Adding a Triangle means editing that method, risking regression in the other branches. The OCP-compliant version gives Shape an abstract area() method, lets each shape implement it, and makes the calculator just sum shape.area() over the list. Adding Triangle is now a new class only; the calculator does not change.
The mechanism behind OCP is polymorphism (or the Strategy pattern). You identify the part that varies, hide it behind an abstraction, and add new variants as new implementations. A note of realism: you cannot be closed against all changes. You choose the axes of variation you expect, often after seeing the first change, following the rule "fool me once". Predicting every possible future is over-engineering.
How to spot violations in code review. For SRP: the class description needs the word "and", or unit tests need many unrelated mocks. For OCP: you keep reopening the same switch statement every sprint, or a new enum value forces edits in several files.
Interviewers often follow up with a real example from your project, so prepare one. A payment module where each new method like UPI or card is a new class implementing PaymentMethod, with no change to checkout logic, is a great and relatable example for Indian students.
class Invoice {
private final double amount;
Invoice(double amount) { this.amount = amount; }
double total() { return amount * 1.18; } // domain rule: 18% GST
}
class InvoicePrinter {
void print(Invoice i) {
System.out.printf("Total with GST: %.2f%n", i.total());
}
}
public class SrpDemo {
public static void main(String[] args) {
new InvoicePrinter().print(new Invoice(1000));
}
}
import java.util.*;
interface Shape { double area(); }
class Circle implements Shape {
private final double r;
Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
}
class Rect implements Shape {
private final double w, h;
Rect(double w, double h) { this.w = w; this.h = h; }
public double area() { return w * h; }
}
class Triangle implements Shape { // added later, nothing else changes
private final double b, h;
Triangle(double b, double h) { this.b = b; this.h = h; }
public double area() { return 0.5 * b * h; }
}
class AreaCalculator {
double total(List<Shape> shapes) {
double sum = 0;
for (Shape s : shapes) sum += s.area();
return sum;
}
}
public class OcpDemo {
public static void main(String[] args) {
List<Shape> shapes = List.of(new Circle(1), new Rect(2, 3), new Triangle(4, 5));
System.out.printf("%.2f%n", new AreaCalculator().total(shapes));
}
}
3.14159 + 6 + 10 = 19.14159, printed with two decimals.
SOLID, SRP, OCP, polymorphism