Picking the best code pattern is usually up to personal preference, however some patterns work better than others depending on the task.
Introduction
- What is a code pattern? A code pattern is a consistent way of structuring your code.
- Why do I need one? It makes your code much easier to understand if your code is written in a consistent format instead of having a singleton here, global instances there, and a static class there.
Notes
- Do your best to only use one code/design pattern for the entire codebase. If you use more than one, the code can get very hard to read very quickly.
Code Patterns
Now it's time to get into the actual design patterns. Each one of these has their own pros and cons, but each should work for most tasks you give it.
Singletons
The only rule for the singleton design pattern is that, when possible, each class should only have one instance. For example, you might have a Robot class. Using the singleton design pattern, that Robot class could have a static method named getInstance. This method returns the global instance, or, if there is no instance, creates one and returns it. Here's how that would look in practice:
public class Robot {
private static Robot robotInstance = null;
public static Robot getInstance() {
if (Robot.robotInstance == null) {
Robot.robotInstance = new Robot();
}
return Robot.robotInstance;
}
private Robot() {
// initialize the robot
}
// ...
}
By doing this, the program ensures that there is only ever one singular instance of the Robot class. If there is no instance, it will create one. Otherwise, it will return the already created instance.
Benefits
- Because there is only one instance, values will change everywhere when changed.
- Only one instance is ever created, which saves memory.
- The instance does not need to be stored in a global state, which avoids having 20
public staticvariables in yourOpMode.
Downsides
- This can be harder to implement in many cases, including getting
OpModespecific values (e.g.HardwareMap). However, it is possible to get around this, but the code is not the cleanest.
Notes
- This can make code much easier to read, but it can also make it much harder to read. Use your own judgement when applying this concept.
Global States
Similar to singletons, these also avoid creating multiple instances of the same class. However, with using global states, it is still possible to create multiple instances. The key difference between global states and singletons is where they are stored. Singletons are stored in the class their own class. Global states are generally stored in the OpMode or another parenting class (e.g. Robot to Outtake).
Benefits
- This is much easier to implement than singletons.
- This works great for quick and dirty prototypes.
Downsides
- This can make code harder to read and debug.
- This does not prevent multiple instances from being created.
Notes
- While this is not a bad design pattern, there is usually a better option.
- This can be a very good option if you only have one or two parenting classes that have fields that store child classes (e.g. one
Robotdefined in yourOpModethat contains anOuttakeand theIntake, both of which store the related motors).
Managers
Instead of making classes that have many fields with child classes, you could take a more functional (as in functional oriented programming) approach. While this is generally not a good design due to Java being an object oriented language, this can function well.
Using this approach, you could store the motors and other hardware in a storage class or just as fields in your OpMode. Then, you could make managers with utility functions that do specific actions when called with an argument. For example:
@Autonomous(name = "Example")
public class ExampleAuto {
public DcMotor motor;
@Override
public void init() {
motor = hardwareMap.get(DcMotor.class, "motor");
}
@Override
public void start() {
MotorManager.rotate(motor, 360);
}
}
Here, we have an example auto mode. Once the mode is started, we call a method on the MotorManager to rotate the motor by 360 degrees. Of course, this assumes we have logic in the MotorManager to calculate ticks from degrees and to stop the rotation once it has completed.
Benefits
- This enables more procedural abstraction.
Downsides
- This goes against Java's core of OOP.
- This can make code way harder to read in the large majority of cases.
- This makes logic way harder to follow and define.
Notes
- This was only included because it is technically possible.
- This is more an example of what not to do when programming a robot.