Singleton

Overview

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is especially useful in robotics for components that represent unique hardware systems, such as subsystems (e.g., drivetrain, elevator).

In FRC, subsystems model distinct parts of the robot, and there should only be one instance of each. Using a Singleton pattern guarantees that only one object exists for each subsystem, avoiding conflicts in control and making coordination between commands and subsystems predictable.


Motivation

Consider a robot's drivetrain. If multiple drivetrain instances existed, commands could conflict or hardware might receive inconsistent signals. Singleton enforces a single source of truth for each subsystem.

Benefits:

  • Prevents multiple conflicting instances

  • Centralizes access logic

  • Simplifies debugging and testing

  • Promotes safe, hardware-accurate design


Structure

In its most basic form, a Singleton class:

  1. Has a private static instance of itself

  2. Provides a public static method to get the instance

  3. Ensures the instance is only created once

public class Elevator extends Subsystem {
    private static Elevator instance = null;

    private Elevator() {
        // Initialization logic
    }

    public static Elevator getInstance() {
        if (instance == null) {
            instance = new Elevator();
        }
        return instance;
    }
}

However, the Subsystem class offers a more centralized and consistent Singleton mechanism:

public static Elevator getInstance() {
    return (Elevator) Subsystem.getInstance(Elevator.class);
}

Here, Subsystem.getInstance(Class) uses reflection and internal caching to enforce singleton behavior across all subsystems without repeating the boilerplate.


Subsystem Singleton in Practice

public class Drivetrain extends Subsystem {
    private final MotorController leftMotor, rightMotor;

    private Drivetrain() {
        leftMotor = new MotorController(1);
        rightMotor = new MotorController(2);
    }

    public static Drivetrain getInstance() {
        return (Drivetrain) Subsystem.getInstance(Drivetrain.class);
    }

    public void drive(double speed) {
        leftMotor.set(speed);
        rightMotor.set(speed);
    }
}

In your RobotContainer or command classes:

Drivetrain.getInstance().drive(0.5);

Common Use Cases in FRC

1. Hardware Subsystems

Drivetrain, Intake, Shooter, Elevator, Arm – all implemented as singletons to ensure consistent access to hardware components.

2. Sensor Interfaces

IMUs, Limelight, Odometry Providers – Singleton patterns prevent double initialization of sensitive hardware.

3. Field Simulators / Robot State

The RobotState class, which tracks robot pose, speed, and field-relative positioning, is often designed as a singleton to ensure global consistency.

Pose2d robotPose = RobotState.getInstance().getLatestPose();

Best Practices

  • Avoid Singleton for stateless utility classes (use static methods instead)

  • Do not use Singleton to replace proper dependency injection in complex systems unless justified

  • Ensure the constructor is private to block external instantiation

  • Prefer lazy initialization unless eager initialization is safer or simpler

Until recently every subsystem was a singelton, I switchted in order to create a cleaner switch between simulation and real robot control via the Strategy Pattern. If you can find a way to cleanly make our subsystems singleton aswell that would be great. (Consider the antiPattern in "Why Use Dependency Injection")

Last updated