diff --git a/docs/core-programming/patterns/dependency-injection.md b/docs/core-programming/patterns/dependency-injection.md index 219b5c3..4b10740 100644 --- a/docs/core-programming/patterns/dependency-injection.md +++ b/docs/core-programming/patterns/dependency-injection.md @@ -51,7 +51,7 @@ Without DI, you would create dependencies manually: ```java // BAD: Hard to test, tightly coupled // Every dependency is hardcoded inside the class -public class ShooterSubsystem() { +public class ShooterSubsystem { private ElectricalContract contract = new CompetitionContract(); // Always this contract private PIDManager pid = new PIDManager(...); // Always these values private MotorController motor = new TalonFX(...); // Always this motor type @@ -269,6 +269,88 @@ If Dagger cannot find a way to provide a dependency, you get a **compile error** +Handing a class what it needs through its constructor, rather than letting it build its own dependencies, is called **constructor injection**. Every `@Inject` constructor you have written is an example of it. + +### 4. Real hardware vs. mocks + +This is where dependency injection earns its keep, and it is the reason your unit tests can run on a laptop with no robot attached. + +There are two components, built from different modules: + +[Source: XbotEdu injection components](https://github.com/Team488/XbotEdu/tree/main/src/main/java/competition/injection/components) + +```java +// On a real robot: real motors, real gamepads +@Singleton +@Component(modules = { RobotModule.class, RealDevicesModule.class, + RealControlsModule.class, CompetitionModule.class }) +public abstract class RobotComponent extends BaseRobotComponent { } + +// In simulation and unit tests: fake devices +@Singleton +@Component(modules = { SimulationModule.class, MockDevicesModule.class, + RealControlsModule.class, SimulatedRobotModule.class }) +public abstract class SimulationComponent extends BaseRobotComponent { } +``` + +Both extend the same `BaseRobotComponent`, so both can produce the same list of objects -- they just build them from different parts. `Robot.java` picks one at startup: + +```java +protected BaseRobotComponent createDaggerComponent() { + if (BaseRobot.isReal()) { + return DaggerRobotComponent.create(); + } else { + return DaggerSimulationComponent.create(); + } +} +``` + +The swap itself lives in the modules. `MockDevicesModule` binds the same factory interface to a mock implementation: + +[Source: SeriouslyCommonLib MockDevicesModule](https://github.com/Team488/SeriouslyCommonLib/blob/main/src/main/java/xbot/common/injection/modules/MockDevicesModule.java) + +```java +@Module +public abstract class MockDevicesModule { + // "When anything asks for a motor controller factory, hand it the mock one" + @Binds + @Singleton + public abstract XCANMotorController.XCANMotorControllerFactory getMotorControllerFactory( + MockCANMotorController.MockCANMotorControllerFactory impl); +} +``` + +**The payoff:** `DriveSubsystem` asks for an `XCANMotorControllerFactory` and never learns which kind it got. On the robot it receives real motors; in your tests it receives fake ones. The subsystem code does not change, and there is no `if (testMode)` branch anywhere in it. + +
+How do tests get objects out of Dagger? + +A test asks the component for what it needs, instead of calling `new`: + +```java +public class TankDriveTest extends BaseDriveTest { + @Test + public void test() { + BaseCommand command = this.getInjectorComponent().tankDriveWithJoysticksCommand(); + // ... the command arrives fully wired, with mock hardware underneath + } +} +``` + +For that to compile, the component has to expose the type. `BaseRobotComponent` declares one abstract method per object a test can request: + +```java +public abstract class BaseRobotComponent extends BaseComponent { + public abstract TankDriveWithJoysticksCommand tankDriveWithJoysticksCommand(); + public abstract DriveToPositionCommand driveToPositionCommand(); + // ... one line per directly-requestable object +} +``` + +Most classes never need an entry here -- an `@Inject` constructor is enough for Dagger to build them as a dependency of something else. You only add a line when a test (or `Robot.java`) needs to ask for that object **directly**. + +
+ ## Scopes [Source: SeriouslyCommonLib SwerveSingleton](https://github.com/Team488/SeriouslyCommonLib/blob/main/src/main/java/xbot/common/injection/swerve/SwerveSingleton.java) diff --git a/docs/core-programming/patterns/providers-factories.md b/docs/core-programming/patterns/providers-factories.md index 638e2d5..e754bc1 100644 --- a/docs/core-programming/patterns/providers-factories.md +++ b/docs/core-programming/patterns/providers-factories.md @@ -2,6 +2,52 @@ Creating objects that need runtime parameters. +## What's a Factory? + +A **Factory** is a class whose only job is to create instances of other objects. + +Some objects are a chore to build. Take a `Car`: to create one you need its Wheels, Doors, Windshield and so on. You would not want to assemble all of those by hand every time you needed a car. + +```java +public class Car { + public Car(Wheel frontLeft, Wheel frontRight, Wheel rearLeft, Wheel rearRight, ...) { + // ... + } +} +``` + +A factory hides that work behind one call: + +```java +public class CarFactory { + public static Car create() { + Wheel frontLeftWheel = new Wheel(); + Wheel frontRightWheel = new Wheel(); + // .. create all other car parts .. + return new Car(frontLeftWheel, frontRightWheel, ...); + } + + // A factory can also make variations + public static Car createSillyCar() { + // .. same idea, but with SquareWheel instead .. + } +} +``` + +```java +Car myCar = CarFactory.create(); +Car mySillyCar = CarFactory.createSillyCar(); +``` + +**You have already used one of these.** Back in the Tank Drive challenge, `DriveSubsystem` got its motors from a factory rather than constructing them itself: + +```java +this.frontLeft = motorControllerFactory + .create(new CANMotorControllerInfo("FrontLeft", 1), this.getPrefix(), "FrontLeft"); +``` + +The rest of this page is about *why* XBot builds objects this way, and how the factories are wired up. + ## The Problem [Source: SeriouslyCommonLib PIDManager](https://github.com/Team488/SeriouslyCommonLib/blob/main/src/main/java/xbot/common/math/PIDManager.java) @@ -128,6 +174,8 @@ The key insight: **Dagger provides the factory, you provide the runtime values.* | `HeadingModule.HeadingModuleFactory` | Heading PID modules | | `XDigitalInput.XDigitalInputFactory` | Digital sensors | | `XLaserCAN.XLaserCANFactory` | Laser distance sensors | +| `XGyro.XGyroFactory` | Gyros | +| `XSolenoid.XSolenoidFactory` | Solenoids | ## Usage Pattern