In this lab, we'll learn how to use the classes we have made in a functioning program.
Open Project 9 - Contacts (OOP) through Codespaces or GitHib Desktop / VS Code
Recall from our previous lessons that a class like Employee serves as a blueprint for creating objects (in this case, employee objects), each equipped with attributes like name and email.
Managing multiple such objects efficiently requires a system that can handle them collectively.
This is where we will create a new class: EmployeeManager. This will act as a digital HR system that manages and tracks all employees:
class EmployeeManager:
def __init__(self):
self.employees = [] # This list will store our Employee objects
The EmployeeManager class has an initializer method (__init__) that sets up a list to hold Employee instances. This list serves as our database for storing employee data. A Python list is ideal for this purpose because it allows us to add, remove, and access employee objects dynamically.
To add employees to our system, we need a method that can do this:
def add_employee(self, first_name, last_name, email):
new_employee = Employee(first_name, last_name, email)
self.employees.append(new_employee)
The add_employee method:
takes individual employee details
creates a new Employee object
appends it to our list
This action represents the 'Create' operation in CRUD (Create, Read, Update, Delete).
The display_employees method iterates over the list of employee objects and prints each one's information.
This is a direct application of the 'Read' operation in CRUD.
def display_employees(self):
for employee in self.employees:
employee.print_details()
Notice that we have called a method called print_details() on the employee object. This is a great advantage of OOP.
We can hide that functionality away in that class, keeping this manager class nice and simple. E.g.
def print_details(self):
print(f"Employee ID: {self.id_number}")
print(f"Name: {self.first_name} {self.last_name}")
print(f"Email: {self.email}")
print("-" * 40) # Separator
In Python, dunder methods (double underscore) are special methods that have double underscores before and after their names (e.g., __init__, __str__). They are designed to be invoked automatically under certain circumstances, and they often provide an elegant way to implement behavior that mimics the behavior of built-in types. For example, the __init__ method is automatically called when a new object is created.
The __str__ method is a dunder method specifically intended to return a string representation of an object when it is printed or converted to a string. This is particularly useful in object-oriented programming for providing a human-readable representation of an object that can be used in debugging, logging, or simply displaying object data in a readable format.
Here's how you could implement the __str__ method in the Employee class to automatically handle the formatting of employee details, instead of having a specific print_details() method:
def __str__(self):
return (f"Employee ID: {self.id_number}\n"
f"Name: {self.first_name} {self.last_name}\n"
f"Email: {self.email}\n"
"-" * 40) # Adds a separator for visual clarity
def display_employees(self):
for employee in self.employees:
print(employee) # This will automatically use the __str__ method
This method demonstrates the 'Update' operation in CRUD by searching for an employee by their ID and updating their email address.
Similar methods could be developed to update all employee details or other specific details.
def update_employee_email(self, employee_id, new_email):
for employee in self.employees:
if employee.id_number == employee_id:
employee.update_email(new_email)
break
Consider how you might improve this algorithm if the employee ID doesn't exist.
The remove_employee method filters out the employee with the specified ID, implementing the 'Delete' operation in CRUD.
def update_employee_email(self, employee_id, new_email):
for employee in self.employees:
if employee.id_number == employee_id:
employee.email = new_email
break
Again, consider how you might improve this algorithm if the employee ID doesn't exist.
Both of the methods above use a technique called linear search. I'm mentioning this now as it is a VERY common pattern that you have used before and will use many times again. Linear search is a straightforward method used to find a specific item in a list. It works by starting at the beginning of the list and checking each item one by one until it finds the item it's looking for or reaches the end of the list.
BEGIN LinearSearch(List, Target)
FOR each item in List
IF item equals Target
RETURN "Item found at position" + index of the item
END IF
NEXT
RETURN "Item not found"
END
Consider using linear search when you're dealing with small datasets or when the list items are unordered and infrequently searched. It's also useful when simplicity is more important than efficiency, or you need a quick and easy solution without additional setup.
In this example, we're finally creating an instance of the EmployeeManager object and adding some employees to it. The system allows adding, displaying, updating, and removing employee details, demonstrating how OOP can simplify complex tasks through encapsulation and modularity.
Also, we are now using the import system to organise our code into multiple files. Notice there are three files below:
This example shows how we use Object-Oriented Programming (OOP) to manage employees efficiently with a class called EmployeeManager.
This class organises all the tasks we need to do with employees—like adding, updating, showing, and removing them—into methods. By doing this, our code is neat and easy to manage because everything related to employees is in one place. This way of organising code makes it simpler to understand and change as needed.
Also, in OOP, the employee data is safely stored and can only be changed using these methods, which helps protect the information. This is different from procedural programming, where the code can be more spread out and the data less secure, especially as the program gets bigger and more complex.
Here we will continue to build on our understanding of UML class diagrams by showing a relationship between two classes:
In this example we have:
An arrow pointing from Employee Manager to Employee
This indicates a directional relationship, as EmployeeManager references Employee objects and not the other way around
In real life, this matches the concept that an employee manager manages employees
Open lab4.py
Complete the following activities:
Copy/paste your Contact class from Lab 2.
Add a check_email method to check if the email contains an '@'
Create a class method get_contact_count to retrieve the number of contacts
Reproduce the instances of the Contact class that you created in Lab 2
Call your new instance method on one Contact and print the result
Use the class method to print out the total number of contacts
Commit and push your code