Publish AI, ML & data-science insights to a global community of data professionals.

Implementing the Coffee Machine Project in Python Using Object Oriented Programming

Understanding classes, objects, attributes, and methods

Photo by Nathan Dumlao on Unsplash

Introduction

The Coffee Machine Project is a simple hands-on project that allows Python practice on loops, conditions, etc. The project is straightforward; we have a coffee machine that has different types of coffee a user may order, along with the ingredients that are required to make them, and their price tag. A user orders a coffee drink, pays in coins, the machine calculates the total, and if the payment is complete, it dispenses the coffee. For a better understanding of this project, check my already published article on Towards Data Science: Implementing the Coffee Machine in Python

In this article, we will build the OOP version of the Coffee Machine in Python.

What is OOP?

Object Oriented Programming is a programming concept that is based on creating Classes and Objects. In simple words, a Class is a template or a blueprint, a defined broad category, and Objects are the individual structures that are created from these Classes. The Class in OOP defines certain features that all the objects that come from this class will have, these are called the Attributes. Moreover, the Class also defined certain functions that it can do. In OOP terminology, these are called Methods.

Suppose we define a class “Cats”. This is a broad category and will include all types of cats as its objects: my cat Figaro, the neighbor’s cat Simba, etc. Each cat will have some individual characteristics like their name, eye color, their breed, etc. These will be coded as their attributes. Moreover, they will also have specific functions like being able to hunt, sleep, play, meow, etc. These will be coded as their methods.

The following is how classes and objects are coded:

class Cat:
    def __init__(self, name, eye_color, fur_color, breed, age, length):
        self.name = name 
        self.eye_color = age 
        self.fur_color = fur_color
        self.breed = breed
        self.age = age
        self.length = length
  

    def hunt(self, animal):
        print(f"My cat {self.name} is hunting a {animal}")


my_cat = Cat("Figaro", "blue", "black and white", "Persian", "2", "48")
print(my_cat.name)

neighbour_cat = Cat("Simba", "hazel", "brown", "Siamese", "3", "50")
neighbour_cat.hunt("mouse")
Understanding Classes, Objects, Attributes and Methods (Image by Author)

We will use the above concept and functionality to build an OOP version of a coffee machine.

Project Working

In my previous article on the Coffee Machine, I thoroughly explained the working of the project. This project will be similar in its working, except that we will utilize Object-Oriented Programming to achieve the same results. Overall, we have the following steps:

Flowchart (Image by Author)

Defining Classes for our Coffee Machine

The first step in this project is to define our classes, one by one, and then we are going to use these blueprints to define objects needed throughout the project.

Defining the “MenuItem” Class

First of all we will define the MenuItem class, that will model each item in our menu, and will use the objects created from it in the next class we will create Menu.

class MenuItem:
    def __init__(self, name, water, milk, coffee, cost):
        self.name = name
        self.cost = cost
        self.ingredients = {
            "water": water,
            "milk": milk,
            "coffee": coffee
        }

Suppose we want to create an object menuitem of the class MenuItem. In that case, we will need to give it the following parameters: name of the item, the amount of water required to make this menu item, the amount of milk required to make this menu item, the amount of coffee required to make this menu item, and the cost of this menu item.

menuitem object created from the MenuItem Class (Image by Author)

This MeniItem class will be used to initialize objects later on.

Defining the “Menu” Class

Next, let us define a class Menu that will contain the details of each item that can be ordered. The object menu can be initialized with the constructor below, and the menu attribute of this class will be a list of the 3 items constructed as objects from the class we constructed earlier MenuItem. We will construct 3 objects from this class with their defined parameters. So, for example, the object latte will require 200 ml of water, 150ml of milk, and 24 g of coffee, and the cost will be $2.5. All this will be modeled inside the class constructor of Menu, which will be used to initialize objects of this class. The __init__ method is always called whenever we create an object.

class Menu:
    def __init__(self):
        self.menu = [
            MenuItem(name="latte", water=200, milk=150, coffee=24, cost=2.5),
            MenuItem(name="espresso", water=50, milk=0, coffee=18, cost=1.5),
            MenuItem(name="cappuccino", water=250, milk=50, coffee=24, cost=3),
        ]

Now we have the Menu attributes, we will also define two methods in it. One is to return the names of the available menu items get_items, and the second method find_drink, is to find the drink that the user selects through the input function.

class Menu:
...

    def get_items(self):
        options = ""
        for item in self.menu:
            options += f"{item.name}/"
        return options

    def find_drink(self, order_name):
        for item in self.menu:
            if item.name == order_name:
                return item
        print("Sorry that item is not available.")

Creating an Object from the “Menu” Class

To put this class and its associated attributes and methods to use, we will first initialize an object menu of the class Menu. We will then use the method get_items to display to the user the items we have in our menu.

menu = Menu()
drinks = menu.get_items()
drink_selected = input(f"Select your drink {drinks}: ")

menu.find_drink(drink_selected)

Once the user has typed their drink, we will check if it is present in the menu object using the Menu class find_drink method. This method checks if the user input is present in the menu, it will return that item, otherwise it will print out to the user that the drink is not available.

Menu Class methods (Image by Author)

So if we give an order of a vanilla shake, the program will simply print out that the item is not available. When the input drink is present in our defined menu, the method find_drink will return the item. Now we can use this item in our coding ahead.

Defining the “CoffeeMaker” Class

Our next step is to define the CoffeeMaker class. The CoffeeMaker class will store the resources specified beforehand, and will have 3 methods that their objects can be used:

  1. report method : This method will allow the management to check the resources in the coffee machine by printing a report of all resources.
  2. is_resources_sufficient method: This method will check if the resources in the coffee machine are sufficient to make the desired drink. It will return True if resources are sufficient; otherwise will let the user know which resource is insufficient.
  3. make_coffee method: The last method will be called when the program’s requirements have been fulfilled, and we have to make the coffee. This piece of code will deduct the resources and dispense coffee.
class CoffeeMaker:
    def __init__(self):
        self.resources = {
            "water": 1000,
            "milk": 1000,
            "coffee": 200,
        }

    def report(self):
        print(f"Water: {self.resources['water']}ml")
        print(f"Milk: {self.resources['milk']}ml")
        print(f"Coffee: {self.resources['coffee']}g")

    def is_resource_sufficient(self, drink):
        can_make = True
        for item in drink.ingredients:
            if drink.ingredients[item] > self.resources[item]:
                print(f"Sorry there is not enough {item}.")
                can_make = False
        return can_make

    def make_coffee(self, order):
        for item in order.ingredients:
            self.resources[item] -= order.ingredients[item]
        print(f"Here is your {order.name} ☕️. Enjoy!")

Defining the “MoneyMachine” Class

Lastly, let us also define the class MoneyMachine. This class will be responsible for the money management. It will process coins to check if payment is made and add the money received to the money bank.

The MoneyMachine class has the following methods:

  1. report method: This method will print how much money we have in our account
  2. process_coins method: This method will process the payment in the form of coins of nickels, dimes, quarters, and pennies and will calculate the total.
  3. make_payment method : This method will be used to check if the payment is made, is it complete or if the user has overpaid; it will return the change.
class MoneyMachine:
    CURRENCY = "$"
    COIN_VALUES = {
        "quarters": 0.25,
        "dimes": 0.10,
        "nickles": 0.05,
        "pennies": 0.01
    }

    def __init__(self):
        self.profit = 0
        self.money_received = 0

    def report(self):
        print(f"Money: {self.CURRENCY}{self.profit}")

    def process_coins(self):
        "Please insert coins.")
        for coin in self.COIN_VALUES:
            self.money_received += int(input(f"How many {coin}?: ")) * self.COIN_VALUES[coin]
        return self.money_received

    def make_payment(self, cost):
        self.process_coins()
        if self.money_received >= cost:
            change = round(self.money_received - cost, 2)
            print(f"Here is {self.CURRENCY}{change} in change.")
            self.profit += cost
            self.money_received = 0
            return True
        else:
            print("Sorry that's not enough money. Money refunded.")
            self.money_received = 0
            return False

The Final Code

Now that we have defined our classes, we will write the final code that will ask the user for their order, check that resources are sufficient, process the coins and payments and dispense the drinks when conditions are satisfied. All this will be done by initializing objects.

money_machine = MoneyMachine()
coffee_maker = CoffeeMaker()
menu = Menu()

is_on = True

while is_on:
    options = menu.get_items()
    choice = input(f"What would you like {options}: ")
    if choice == 'off':
        is_on = False
    elif choice == 'report':
        coffee_maker.report()
        money_machine.report()
    else:
        drink = menu.find_drink(choice)
        if coffee_maker.is_resource_sufficient(drink) and money_machine.make_payment(drink.cost):
            coffee_maker.make_coffee(drink)

In our final code, we have created a while loop that runs continuously unless someone from management commands it to stop. We have done this using the is_on boolean variable, which changes to False when the management types “off” as input to the order prompt.

Moreover, just like we did in our coffee machine project without OOP, we have also added the report option that gives report of all the resources in the coffee machine, and the money in the account

The program will run as follows: It will prompt the user to order a drink by displaying the menu items. If the item selected by the user is in the list, it will first check if resources are sufficient, then ask for payment and check if the payment is complete, and then process the order. All this has been done using OOP concepts of classes and objects.

Conclusion

While we have not fully explored the capabilities of OOP in this project, we have successfully decreased our lines of code by introducing classes and creating objects from them. When it comes to more complex projects with a number of different objects, all sharing a common blueprint, the inclusion of OOP concepts come in way more handy.

Nevertheless, this coffee machine project using OOP has been a useful tutorial to grasp the basics of Object Oriented Programming. If you have any suggestions or can think of an alternative flow of tasks for this project, feel free to share. Till then, enjoy your coffee! 😀

Photo by Mike Kenneally on Unsplash

Towards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.

Write for TDS

Related Articles