2.4.7 - Inline Commands
So far we’ve learned two methods to create complex commands:
Subclass
Commandto create super-commands that do a lot.Use command compositions to combine smaller commands to more complex commands. These smaller commands, however, still have to be made through subclassing
Command.
In either option, we have to subclass the Command base class directly,
which causes a few problems that we’ve already gone over:
Highly verbose commands
Lots of boilerplate code
Not very easy to understand (not very declarative)
However, there’s one way to create commands that doesn’t involve subclassing
Command at all. These types of commands are referred to as “inline
commands” and make writing commands a lot more simple. Combining the abilities
of command factories, decorators, and compositions, we can make much nicer
commands when compared to anything else we can do.
Return to 2.4.3 - Subclassing Commands if you forgot what the command looks like if
we subclass Command directly. Here’s what the logical flow of the command
looked like:
Set the intake’s position reference to the intake position.
Wait for the intake to reach the setpoint.
Start the intake
Wait for a piece
When the command ends (either by interruption or the end condition (waiting for a piece), set the intake’s position reference to the stow position and stop the intake from continuing to intake the piece.
This really isn’t very complex, but the subclassing approach makes large and verbose files with lots of boilerplate. But we can change that. Here’s what the exact same logic looks like when we use command factories, decorators, and compositions.
Command intakeCommand = Commands.sequence(
intake.runOnce(intake::setIntakePosition),
Commands.waitUntil(intake::atPosition),
intake.runOnce(intake::setIntakeSpeed),
Commands.waitUntil(intake::hasPiece))
.finallyDo(intake::stop)
.finallyDo(intake::setStowPosition);
Note
We could have put both intake.stop() and
intake.setStowPosition() in a lambda and only used one call to
finallyDo like so:
/* snip */
.finallyDo(() -> {
intake.stop();
intake.setStowPosition();
});
The reason this wasn’t done is because I prefer the look of the other way. There’s less indentation.
Overall, creating commands in this style can make commands much easier to work
with and understand. It’s much more declarative than subclassing
Command in their own separate file.
However, there’s one question that arises as a result of this style of creating commands. We don’t create a new class for each command, so where should we be putting all these commands?
There is a variety of options for this, but here are a few common options:
RobotContaineris a good place to create commands. You also have to bind those commands to buttons, so it’s natural to create the commands there too.Creating a custom class that acts as a static factory that creates commands is another good option. Static methods like
buildIntakeCommand(Intake intake)separate the creation of subsystems and commands from the logic inside them.Creating subsystem-specific commands inside of each subsystem and exposing them through factory methods is a third way to go. This eliminates a lot of repetitive code and allows us to hide more of the subsystem’s inner features.
More on this topic can be found in the advanced section.