The SOLID principles
SOLID is an acronym for five object-oriented design principles, popularised by Robert C. Martin. They help keep code flexible, understandable, and easy to change.
Single responsibility principle
A class should have one reason to change. A Report class that calculates figures, formats them as PDF, and emails them has three reasons to change. Splitting it into ReportCalculator, PdfFormatter, and EmailSender means a change in formatting does not risk breaking calculations.
Open/closed principle
Software entities should be open for extension but closed for modification. Instead of editing a long if-else chain every time a new shape is added, define a Shape interface with an area method; adding a Triangle means writing a new class, not modifying existing, tested code.
Liskov substitution principle
Named after Barbara Liskov, it says objects of a subclass should be usable anywhere their base class is expected without breaking correctness. The classic violation: Square extends Rectangle, but setting a square's width also changes its height, surprising code that expects rectangles' sides to be independent.
Interface segregation principle
Clients should not be forced to depend on methods they do not use. A huge Machine interface with print, scan, fax, and staple forces a simple printer to implement fax. Smaller interfaces, Printer, Scanner, and Fax, let classes implement only what they support.
Dependency inversion principle
High-level modules should not depend on low-level details; both should depend on abstractions. An OrderService should depend on a PaymentGateway interface, not directly on a specific payment company's library. The concrete implementation is supplied from outside, often through the constructor: dependency injection.
Dependency injection in practice
With constructor injection, new OrderService(new CardGateway()) in production and new OrderService(new FakeGateway()) in tests use the same class with different collaborators. Frameworks such as Spring automate wiring. Injection makes code testable, because real databases or payment systems can be replaced by fakes.
Using principles wisely
SOLID principles are guidelines, not laws. Applying them mechanically can create many tiny classes and layers of indirection that make simple code hard to follow. Related advice includes KISS (keep it simple), YAGNI (you aren't gonna need it: do not build features before they are needed), and DRY (don't repeat yourself).
Worked example
A NotificationService originally contains if (type == EMAIL) sendEmail() else if (type == SMS) sendSms(). Adding push notifications means editing it again, violating open/closed. Refactored: interface Notifier { void send(Message m); } with EmailNotifier, SmsNotifier, and PushNotifier classes; the service holds a list of Notifiers injected through its constructor, satisfying single responsibility, open/closed, and dependency inversion.
Key takeaways
SOLID: single responsibility (one reason to change); open/closed (extend without modifying); Liskov substitution (subclasses must honour base-class expectations; square-rectangle is the classic violation); interface segregation (small, focused interfaces); dependency inversion (depend on abstractions). Dependency injection supplies collaborators from outside and makes testing easy. Apply principles with judgement, alongside KISS, YAGNI, and DRY.
Flashcard
Five OO design principles
Check your understanding
An InvoicePrinter class must be edited every time a new output format is added. Which principle does this most directly violate?
