Object-oriented calorie counter

Posted on


Object-oriented program for a calorie counter sporting polymorphic
methods for both genders.

Principles to adhere to:

  • Uniform Access Principle
  • Polymorphism
  • Dependency inversion principle through the Factory Method pattern
  • Single responsibility principle

Concerns for future extension:

  • BPM curve for exercise acts of longer duration, can this be transparently added?

Other concerns:

  • Unit prefixes for names, e.g. weight_kg, exercise_duration_min. Are these bad?
  • Overengineered? Yes, I’m not going to argue against that; we don’t have more than one method in our classes which eliminates the need to have them at all. Assuming we had more than one method, however, is it still overdone?
  • Are any of the principles I claimed to follow not followed or properly followed? Especially thinking of the Factory Method pattern.

By the way, the formula for calculating calories burned doesn’t seem to be right, but that doesn’t really matter for our purposes.

# oo-calorie_counter.py
from abc import ABC, abstractmethod

def main():
    """Setup and initialization.
    person_attributes = get_person_attributes()
    person = PersonFactory.make_person(person_attributes)

    print("You've burnt approximately {0} calories".format(round(person.calories_burned, 1)))

def get_person_attributes():
    """Prompts a user for their details
    person_attributes = {
        'gender': input('Your gender: '),
        'weight_kg': int(input('Your weight in Kg: ')),
        'age': int(input('Your age: ')),
        'bpm': int(input('Your heart rate: ')),
        'exercise_duration_min': int(input('Your your exercise duration in minutes: '))

    return person_attributes

class PersonFactory:
    def make_person(person_attributes):
        Instantiate the proper a person of proper gender based on the provided attributes.
            person_attributes: A dictionary containing the keys:
            ('gender', 'age, 'weight_kg', exercise_duration_min, bpm)

        gender = person_attributes['gender']
        del person_attributes['gender']

        if gender.lower() == 'male':
            return Male(**person_attributes)
        elif gender.lower() == 'female':
            return Female(**person_attributes)
            raise RuntimeError('Unknown gender')

class Person(ABC):
    def __init__(self, weight_kg, bpm, age, exercise_duration_min):
        self.weight_kg = weight_kg
        self.age = age
        self.bpm = bpm
        self.exercise_duration_min = exercise_duration_min

    def calories_burned(self):
        raise NotImplementedError

class Male(Person):
    def calories_burned(self):
        return ((self.age * 0.2017) - (self.weight_kg * 0.09036) + (self.bpm * 0.6309) - 55.0969) * self.exercise_duration_min / 4.184

class Female(Person):
    def calories_burned(self):
        return ((self.age * 0.074) - (self.weight_kg * 0.05741) + (self.bpm * 0.4472) - 20.4022) * self.exercise_duration_min / 4.184

if __name__ == '__main__':


I like your code. It is easy to understand and pretty straight-forward.

Regarding your question of the measuring units: yes, every variable should have them since you are not operating exclusively on SI units. You have calories (instead of joule) and years and minutes (instead of seconds).

I would just change some of the smaller details, from top to bottom:

  • The doc comment for main is wrong. That function not only initializes the program, it is the whole program, including computation and output.

  • Rename get_person_attributes to read_person_attributes or input_person_attributes. Functions called get_* usually don’t have side-effects and are usually not complicated.

  • The gender prompt could be more specific: 'Your gender (male, female): '

  • The official spelling for kilogram is kg, not Kg. Although kilo is a large multiplier, it is the only one with a lowercase letter.

  • The prompt for the heart rate could include in bpm.

  • The Your your is a typo.

  • In make_person, the proper a person of proper gender sounds wrong (though I’m not a native English speaker).

  • You compute gender.lower() twice, which is unnecessary.

  • Do you prefer burned or burnt? Choose one and use it everywhere.

Having each of the complicated formulas in a single line is very good. One might be attempted to split it into several small formulae or extract the factors to a separate class CaloriesCalculatorFactors, but that would make the code harder to understand.

Leave a Reply

Your email address will not be published. Required fields are marked *