Why Use Dependency Injection?
Dependency Injection (DI) is a design principle where objects receive their dependencies from an external source rather than creating them internally. In the context of FRC and robot subsystems, this pattern helps us write cleaner, more modular, and more testable code.
The Problem with Direct References
When a subsystem directly creates or accesses another subsystem, it introduces tight coupling. For example:
public class Shooter {
private final Intake intake = new Intake(); // 🚫 Bad
}
Why this is bad:
Tightly coupled: Changes to
Intake
may force changes inShooter
.Hard to test: You can’t replace
Intake
with a fake or mock.Hidden dependencies: It’s unclear what external systems the class depends on.
Lifecycle issues: Creating a new instance may interfere with the actual robot code using a different instance.
The Dependency Injection Solution
Instead of constructing dependencies internally, we inject them through the constructor (or a factory):
public class Shooter {
private final Intake intake;
public Shooter(Intake intake) {
this.intake = intake; // ✅ Good
}
}
Benefits:
Loose coupling: Shooter can work with any class that behaves like an Intake.
Testability: Easily pass in mock or fake subsystems.
Explicit dependencies: It's clear what the class needs to function.
Flexible structure: Easier to refactor and swap implementations.
Example in Robot Code
Here’s how this pattern might look in a RobotContainer
setup:
public class RobotContainer {
private final Intake intake = new Intake();
private final Shooter shooter = new Shooter(intake);
}
By constructing and wiring everything in one place (RobotContainer
), we:
Control the system architecture from a central location.
Avoid surprises or duplicated instances.
Make it easier to debug and understand the flow of data.
Why It’s Especially Useful in FRC
Your subsystems often rely on shared sensors or motors.
Logging, replay, and simulation are easier when subsystems are isolated and injectable.
You can swap in simulations or mocks without rewriting the entire system.
More scalable as your robot grows in complexity.
Anti-Patterns to Avoid
Avoid things like:
SubsystemA.getInstance()
from insideSubsystemB
(singleton misuse).Calling
new Subsystem()
inside other subsystems.Statically referencing global hardware components.
These lead to spaghetti code, where everything is tangled and hard to debug or refactor.
Last updated