I have some interconnected entities:
For instance push ups, pull ups, squats. Each exercise has a name, the muscles involved (chest, legs ...), the executions steps and the category type.
It is an enum that represents how is this exercise measured.
bodyweightReps
type as you
need to measure only the repetitions performed.weightReps
type as you record both weight on the barbell and the repetitions performed.bodyweightReps
needs to record only 1 parameter repetition
,weightReps
needs 2, 1 for weight
and 1 for repetition
).It is an activity performed in a workout. It has an exercise, some notes and a list of workout sets performed
It is a set of a workout activity. It has a rest time and a list of measures for the values stored (for instance number of repetitions performed, weight used ...).
The measures
of the workout set depends on the parent Workout activity Exercise Category, as a set of a Workout Activity with "Barbell Squat", needs to measure the weight and the repetitions performed, as the exercise category is weightReps
.
As you can see, a WorkoutActivity has an exercise, and the structure of the child WorkoutSet measures property depends on the parent WorkoutActivity Exercise category property.
enum ExerciseType {
weightReps,
bodyWeightReps,
distance,
time
}
class Exercise {
String name;
List<String> executionSteps;
ExerciseType category;
}
class Workout {
String name
List<WorkoutActivity> activities;
}
class WorkoutActivity {
String note;
Exercise exercise;
List<WorkoutSet> sets;
}
class WorkoutSet {
int restTime;
List<double> measures;
}
The above structure doesn't guarantee that the WorkoutSets of a workout activity whose exercise category type is bodyweightReps
have only one measure nor that if the category type is weightReps
they have 2 measures.
This issue raises some connected problems that can't be ignored.
Is there a better way to structure the entities so that the measures of the child WorkoutSet can be forced to follow the parent Workout Activity Exercise Category type structure?
In .NET 5 a couple of new code analysis attributes have been introduced, like MemberNotNullWhen
. Which could be helpful if you would have dedicated fields inside the WorkoutSet
for weight
and repetition
measures.
Since we have a collection of measures and we are talking about different programming language, let me start with a simple example.
For the sake of simplicity I moved the ExerciseType
to the WorkoutSet
abstract class WorkoutSet
{
ExerciseType category;
int restTime;
List<double> measures;
public sealed class WeightReps : WorkoutSet
{
public WeightReps (int restSeconds, double repetition, double weight )
{
this.category = ExerciseType.weightReps;
this.restTime = restSeconds;
this.measures = new() { repetition, weight };
}
}
public sealed class BodyWeightReps : WorkoutSet
{
public BodyWeightReps (int restSeconds, double repetition )
{
this.category = ExerciseType.bodyWeightReps;
this.restTime = restSeconds;
this.measures = new() { repetition };
}
}
}
This would allow you to create either a WeightReps
or a BodyWeightReps
class. Their constructors are receiving the required number of parameters and they know how to initialize a given instance. You can not directly create a WorkoutSet
.
new WorkoutSet.BodyWeightReps((int)data.rest.TotalSeconds, data.repetition)
new WorkoutSet.WeightReps((int)data.rest.TotalSeconds, data.repetition, data.weight);
In case of C# you can push this idea a bit further with implicit operators to convert automatically a ValueTuple
to a WorkoutSet
derived class.
public static implicit operator WorkoutSet( (TimeSpan rest, double repetition) data )
=> new WorkoutSet.BodyWeightReps((int)data.rest.TotalSeconds, data.repetition);
public static implicit operator WorkoutSet( (TimeSpan rest, double repetition, double weight ) data )
=> new WorkoutSet.WeightReps((int)data.rest.TotalSeconds, data.repetition, data.weight);
usage
WorkoutSet bodyWeightReps = (TimeSpan.FromMinutes(0.5), repetition: 1.0d);
WorkoutSet weightReps = (TimeSpan.FromSeconds(42), repetition: 1.0d, weight: 2.0d);
Here you can find a working example: https://dotnetfiddle.net/P4rf2P