Lesson 13.2: Extending Parent Classes for Modular Hardware Subsystems
When Encapsulation Alone Is Not Enough
Lesson 13.1 showed how to move a single mechanism into its own class. Many robots use multiple mechanisms that share structural similarity. A robot might have two independent arms, both of which use a DcMotor and a DigitalChannel, both of which need the same zero-power behavior configured, and both of which expose the same general interface of move() and stop(). Without inheritance, you write two classes that are nearly identical, maintaining two copies of the same logic. When a bug appears or the configuration changes, you fix it in one place and forget to fix it in the other.
Inheritance provides a formal mechanism for expressing this shared structure. You write a parent class that contains everything common to all mechanisms of a given type, then write child classes that add only what is different. A bug fix in the parent class propagates automatically to every child.
The extends Keyword and the "Is-A" Relationship
In Java, the extends keyword creates an inheritance relationship. When you write class ArmMechanism extends BaseMotorMechanism, you are declaring that ArmMechanism is a more specific version of BaseMotorMechanism. Every ArmMechanism object inherits all of the methods and member variables of BaseMotorMechanism without needing to redeclare them.
The relationship is described as "is-a": an ArmMechanism is a BaseMotorMechanism. This is different from the "has-a" relationship of composition where one class holds a reference to another. Inheritance is the right choice when child classes represent specialized versions of the same thing. Composition is the right choice when one class uses another as a component.
Two access modifiers are critical here. Variables in the parent class marked private are not accessible to child classes. Variables marked protected are accessible to child classes but still hidden from all other code. For hardware objects that child classes may need to interact with directly, protected is the appropriate choice.
The super() Call
When a child class has a constructor, Java requires that the parent class constructor runs first. This is enforced through the super() call, which must be the first statement in the child constructor. If the parent constructor requires arguments, those arguments must be passed through super(). If the parent has no constructor (relying on the default), the super() call can be omitted and Java inserts it implicitly.
In FTC mechanism classes, where initialization is typically handled by an init(HardwareMap hwMap) method rather than a constructor, super() calls are less common. The more important inheritance pattern is calling super.init(hwMap) at the start of the child's init() method to ensure the parent's hardware mapping runs before any child-specific setup.
Annotated Code
package org.firstinspires.ftc.teamcode.mechanisms;
import com.qualcomm.robotcore.hardware.DcMotor;
import com.qualcomm.robotcore.hardware.HardwareMap;
/**
* Parent class defining a generic single-motor mechanism.
* Child classes inherit the motor and basic power control.
*/
public class BaseMotorMechanism {
// protected: child classes can access this, external code cannot
protected DcMotor motor;
public void init(HardwareMap hwMap, String motorName) {
motor = hwMap.get(DcMotor.class, motorName);
motor.setZeroPowerBehavior(DcMotor.ZeroPowerBehavior.BRAKE);
motor.setPower(0);
}
public void stop() {
motor.setPower(0);
}
}
package org.firstinspires.ftc.teamcode.mechanisms;
import com.qualcomm.robotcore.hardware.HardwareMap;
/**
* Child class for a specific arm mechanism.
* Inherits motor mapping and stop() from BaseMotorMechanism.
* Adds arm-specific lift and lower behavior.
*/
public class ArmMechanism extends BaseMotorMechanism {
private static final double LIFT_POWER = 0.7;
private static final double LOWER_POWER = -0.4;
@Override
public void init(HardwareMap hwMap, String motorName) {
// Ensure the parent's init runs first to map the motor
super.init(hwMap, motorName);
// Child-specific setup can follow here
motor.setDirection(DcMotor.Direction.REVERSE);
}
public void lift() {
motor.setPower(LIFT_POWER);
}
public void lower() {
motor.setPower(LOWER_POWER);
}
// stop() is inherited from BaseMotorMechanism -- no need to rewrite it
}
// Usage in an OpMode:
private ArmMechanism arm = new ArmMechanism();
@Override
public void init() {
arm.init(hardwareMap, "arm_motor");
}
@Override
public void loop() {
if (gamepad1.dpad_up) arm.lift();
else if (gamepad1.dpad_down) arm.lower();
else arm.stop(); // inherited method
}
Fill-in-the-Blank Practice
- The Java keyword used to create a child class from a parent is
__________. - To call a parent class method from within a child class, you use the
__________keyword followed by a dot and the method name. - A parent class variable marked
__________is accessible to child classes but hidden from all unrelated code.
Show answers
extendssuperprotected
Template Challenge
Robot Scenario: You have a BaseServoMechanism parent class that maps a Servo and provides a setPosition(double pos) method. Write a GrabberMechanism child class that extends it and adds two constants, OPEN_POS = 0.1 and CLOSED_POS = 0.9, along with open() and close() methods that call setPosition().
package org.firstinspires.ftc.teamcode.mechanisms;
import com.qualcomm.robotcore.hardware.HardwareMap;
import com.qualcomm.robotcore.hardware.Servo;
// Parent class -- do not modify
public class BaseServoMechanism {
protected Servo servo;
public void init(HardwareMap hwMap, String servoName) {
servo = hwMap.get(Servo.class, servoName);
}
public void setPosition(double pos) {
servo.setPosition(pos);
}
}
package org.firstinspires.ftc.teamcode.mechanisms;
// INSERT CODE HERE: Declare GrabberMechanism extending BaseServoMechanism
{
// INSERT CODE HERE: Declare OPEN_POS and CLOSED_POS as private static final doubles
// INSERT CODE HERE: Write open() and close() methods that call setPosition()
}
Show answer
public class GrabberMechanism extends BaseServoMechanism {
private static final double OPEN_POS = 0.1;
private static final double CLOSED_POS = 0.9;
public void open() {
setPosition(OPEN_POS);
}
public void close() {
setPosition(CLOSED_POS);
}
}
Ready to move on?
Sign in with Google to save your progress with Telemark, or continue without saving.