SOLID Principles in Practice
/ 2 min read
Table of Contents
SOLID principles get talked about a lot, but I see them misapplied more often than used properly. They’re guidelines, not rules, and sometimes breaking them makes sense.
Single Responsibility Principle (SRP)
Class should have one reason to change. Sounds easy but defining “one responsibility” is tricky.
We had Robot class handling sensor data, motion control, safety checks, and logging. Every time we changed safety protocols or sensor calibration, we touched the Robot class. We split it into Robot (data), SafetyValidator, MotionController, SensorLogger. Now changes have clear boundaries.
But be careful not to over-split. I’ve seen codebases with hundreds of tiny classes where simple operations need 10 files. If classes always change together, maybe they belong together.
Open/Closed Principle (OCP)
Add new features without modifying existing code. Use interfaces and polymorphism.
Our conveyor system started with only belt conveyors. Instead of modifying existing code for robotic arms, we made MaterialHandler interface with different implementations. Adding new handling methods doesn’t touch existing logic.
Liskov Substitution Principle (LSP)
Subclasses should work wherever parent class works. This is about behavior, not just method signatures.
Classic broken example: Square inheriting from Rectangle. Code expects to set width and height independently, but Square changes both together. The contract is broken.
We had similar issue with sensors - base class returned values immediately, but some sensors had warm-up time. Fixed by making all sensors return futures consistently.
Interface Segregation Principle (ISP)
Don’t force clients to depend on methods they don’t use.
Our IRobotController had motion control, safety monitoring, diagnostics, calibration. Motion planning only needed movement but had to import everything. Split into IMotionControl, ISafetyMonitor, IDiagnostics. Clients depend only on what they use.
Dependency Inversion Principle (DIP)
Depend on interfaces, not concrete classes.
Our robot planner class created sensor connections directly - impossible to test. Added ISensorProvider interface and injected it. Now we inject mock for testing, swap sensor types without changing path planning logic.
When to Break Them
Small stable codebases don’t need all the abstraction. Performance-critical code sometimes needs direct dependencies. Rapidly changing requirements might benefit from simple implementation first, refactor later.
I use SOLID as refactoring guidelines, not upfront design rules. Hard to test? Check SRP and DIP. Copy-pasting code for similar features? Check OCP.
The goal is maintainable code, not perfect adherence to principles.