Module 14 - Introducing Object-Oriented Programming Header

Module 14 - Introducing Object-Oriented Programming

Introducing OOP

Imagine you have a toolbox filled with different types of tools – hammers, screwdrivers, pliers, and wrenches. Each tool has a specific purpose and set of actions it can perform. In the real world, objects are like those tools, and OOP is a way of organizing and managing them in programming.

What is an Object?

An object is a fundamental concept in OOP. It represents a specific thing or entity that has both data (attributes) and behavior (actions/methods). Just like a tool in your toolbox, an object in programming could be anything – a person, an animal, a car, or even an abstract concept like a book or a bank account.

Classes: Blueprints for Objects

A class is like a blueprint or template that defines the structure and behavior of an object. It serves as a guide for creating objects with common characteristics. For example, if you want to create multiple cars in a computer program, you would define a class called "Car" that specifies the properties (attributes) a car can have, like color, brand, and model, as well as the actions (methods) a car can perform, like driving or honking.

Creating Objects from Classes

Once you have a class defined, you can create multiple objects (instances) based on that class. Each object is like a unique tool in your toolbox, following the blueprint you defined in the class. Each car you create using the "Car" class will have its own specific color, brand, and model, just like how each tool in your toolbox may have different sizes and functions.



Classes and Objects

Let's dive deeper into the concepts of Object-Oriented Programming (OOP) in Python with real-world examples.

Think of a class as a blueprint, and an object as a house built from that blueprint. A class defines the structure and behavior of objects, similar to how a blueprint specifies the layout and features of a house. The class contains attributes (variables) that represent the characteristics of the object and methods (functions) that define what the object can do.

Real-world example:

Consider the Car class. Each car on the road is an object created from the Car class blueprint. The attributes of a car object might include its make, model, year, and color. The methods might include start(), stop(), and accelerate() to control the car's actions.  So attributes hold information. about each car, and methods represent things the car can do. Another way to think about methods is that they are functions that live inside the class, representing things that the class can do.

class Car:
def __init__(self, make, model, year, color):
self.make = make
self.model = model
self.year = year
self.color = color

def start(self):
print(f"The {self.make} {self.model} has started.")

def stop(self):
print(f"The {self.make} {self.model} has stopped.")

def accelerate(self):
print(f"The {self.make} {self.model} is accelerating.")


Attributes and Methods

Let's dig a little deeper into attributes and methods…

Attributes represent the data or properties of an object, and methods represent the actions or behaviors the object can perform. Attributes are like characteristics of an object, and methods are like actions that object can take.

Real-world example:

Imagine a Person class. Each person has attributes such as name, age, and occupation, which describe them. Methods could be speak(), eat(), and work(), representing the actions a person can do.

class Person:
def __init__(self, name, age, occupation):
self.name = name
self.age = age
self.occupation = occupation

def speak(self):
print(f"Hello, my name is {self.name}.")

def eat(self):
print(f"{self.name} is eating.")

def work(self):
print(f"{self.name} is working as a {self.occupation}.")


The Constructor Method

The constructor method is a special method that initializes the attributes of an object when it is created. It is like a factory that sets up the object based on the blueprint (class). In other words, we can create a constructor method so that we can easily set the value of attributes for each object as they are created, very similar to the way we pass arguments to a function when it is called.

Real-world example:

Imagine a Dog class. When you bring home a new puppy, you use the constructor to give it a name and age, and those become its attributes.

class Dog:
def __init__(self, name, age):
self.name = name
self.age = age

def bark(self):
print(f"{self.name} says, 'Woof! Woof!'")

# Creating two objects based on the dog class:
poodle = Dog("Sam", 8)
lab = Dog("Max", 4)


Encapsulation

In OOP, encapsulation means bundling data (attributes) and actions (methods) that belong together within a single object. This helps keep the code organized and prevents accidental changes to the data from outside the object. It's like keeping all the parts of a tool together inside its own toolbox to avoid losing or misusing them.

Real-world example:

Consider a BankAccount class. Encapsulation ensures that other parts of the program cannot directly change the account balance; they can only use methods like deposit() and withdraw() to access it securely.

class BankAccount:
def __init__(self, account_number, balance):
self._account_number = account_number
self._balance = balance

def deposit(self, amount):
self._balance += amount

def withdraw(self, amount):
if amount <= self._balance:
self._balance -= amount
else:
print("Insufficient funds.")

These concepts provide a powerful foundation for building complex applications by organizing data and behavior into meaningful objects. As you progress in your programming journey, you will find OOP to be a crucial skill for solving real-world problems in a systematic and scalable way.



Using a List to Organize Objects

At times, if you are creating many objects from a single class, it is useful to group them together in a single list variable. The following example will walk you through this process, and help you see how to implement it in code.

Let's walk through the process of creating a Fruit class to represent different types of fruits in a fruit stand. Then, we'll use a list variable to organize a group of objects into the fruit stand. We'll also demonstrate how to add and subtract fruits from the inventory using methods in the class.

Step 1: Creating the Fruit Class

The Fruit class will represent individual types of fruits in the fruit stand. Each fruit will have attributes like name, quantity, and price. We'll also create methods to add and subtract fruits from the inventory.

class Fruit:
def __init__(self, name, quantity, price):
self.name = name
self.quantity = quantity
self.price = price

def add_fruit(self, amount):
self.quantity += amount

def remove_fruit(self, amount):
if amount <= self.quantity:
self.quantity -= amount
else:
print(f"Not enough {self.name} in the inventory.")

Step 2: Creating the Fruit Stand

Now, let's create a list variable fruit_stand to organize the group of fruit objects in the fruit stand. We can add different types of fruits as objects to the fruit_stand list.

# Create a list to represent the fruit stand inventory
fruit_stand = []

# Add different types of fruits to the fruit_stand list
fruit_stand.append(Fruit("Apple", 10, 1.50))
fruit_stand.append(Fruit("Banana", 15, 0.75))
fruit_stand.append(Fruit("Orange", 20, 1.25))

Step 3: Interacting with the Fruit Stand

Now that we have set up the Fruit class and created the fruit_stand list, we can interact with the fruit stand by adding and subtracting fruits from the inventory using the methods in the Fruit class.

# Display the current fruit stand inventory
def display_inventory():
print("Fruit Stand Inventory:")
for fruit in fruit_stand:
print(f"{fruit.name} - Quantity: {fruit.quantity}, Price: ${fruit.price:.2f}")

# Add more fruits to the inventory
def add_fruits_to_inventory(fruit_name, amount):
for fruit in fruit_stand:
if fruit.name == fruit_name:
fruit.add_fruit(amount)
print(f"Added {amount} {fruit.name}(s) to the inventory.")
return
print(f"{fruit_name} not found in the fruit stand.")

# Remove fruits from the inventory
def remove_fruits_from_inventory(fruit_name, amount):
for fruit in fruit_stand:
if fruit.name == fruit_name:
fruit.remove_fruit(amount)
print(f"Removed {amount} {fruit.name}(s) from the inventory.")
return
print(f"{fruit_name} not found in the fruit stand.")

# Display the initial inventory
display_inventory()

# Add more fruits to the inventory
add_fruits_to_inventory("Apple", 5)
add_fruits_to_inventory("Grapes", 8)

# Display the updated inventory
display_inventory()

# Remove fruits from the inventory
remove_fruits_from_inventory("Banana", 10)
remove_fruits_from_inventory("Orange", 25)

# Display the final inventory
display_inventory()
 

Here is the resulting output:

Fruit Stand Inventory:
Apple - Quantity: 10, Price: $1.50
Banana - Quantity: 15, Price: $0.75
Orange - Quantity: 20, Price: $1.25
Added 5 Apple(s) to the inventory.
Grapes not found in the fruit stand.
Fruit Stand Inventory:
Apple - Quantity: 15, Price: $1.50
Banana - Quantity: 15, Price: $0.75
Orange - Quantity: 20, Price: $1.25
Removed 10 Banana(s) from the inventory.
Not enough Orange in the inventory.
Fruit Stand Inventory:
Apple - Quantity: 15, Price: $1.50
Banana - Quantity: 5, Price: $0.75
Orange - Quantity: 20, Price: $1.25

In this example, we created a fruit stand inventory using a list variable (fruit_stand) containing different fruit objects. We then interacted with the fruit stand by adding and removing fruits from the inventory using the methods defined in the Fruit class.

By organizing fruit objects into a list, we can easily manage the inventory of the fruit stand and perform various operations on the individual fruit items. The use of a class for fruits allows us to encapsulate the data and behavior related to each fruit, making the code more organized and maintainable.



Aggregation Classes

In object-oriented programming (OOP) with Python, aggregation is a fundamental concept that represents a "whole-part" relationship between classes. It allows one class to contain and manage instances of another class, creating a more complex structure. In aggregation, the "whole" class, known as the container or aggregator, holds references to the "part" classes, which are the objects being aggregated. These part classes can exist independently and may have their own lifecycle, even if the container class is destroyed. Aggregation is a way to model real-world relationships where one object is composed of or contains other objects. In Python, this relationship is established by including instances of one class within another class as attributes, effectively forming a hierarchical or nested structure.

For instance, a Library class can aggregate Book objects as its parts. Each book is a separate object with its own attributes and methods, but they are managed collectively by the library. Aggregation provides a flexible and modular way to structure code, making it easier to manage complex systems by breaking them down into smaller, reusable components. It enhances code readability, maintainability, and reusability, promoting the principles of encapsulation and modular design in OOP.

Here is a code example of how it would work:

class Book:
def __init__(self, title, author):
self.title = title # Initialize the title attribute with the provided title
self.author = author # Initialize the author attribute with the provided author

def __str__(self):
return f"{self.title} by {self.author}" # Define a string representation for the book object

class Library:
def __init__(self):
self.books = [] # Initialize an empty list to hold Book objects (aggregation)

def add_book(self, book):
self.books.append(book) # Add a Book object to the list of books

def remove_book(self, book):
if book in self.books:
self.books.remove(book) # Remove a Book object from the list of books if it exists
else:
print(f"{book} is not in the library.")

def list_books(self):
for i, book in enumerate(self.books, start=1):
print(f"{i}. {book}") # List the books in the library with their index

# Create a Library object
library = Library()

while True:
print("\nLibrary Menu:")
print("1. Add a Book")
print("2. Remove a Book")
print("3. List Books")
print("4. Quit")

choice = input("Enter your choice: ")

if choice == "1":
title = input("Enter the book title: ")
author = input("Enter the author: ")
new_book = Book(title, author) # Create a new Book object
library.add_book(new_book) # Add the new book to the library
print(f"{new_book} has been added to the library.")

elif choice == "2":
print("Books in the Library:")
library.list_books()
book_index = int(input("Enter the number of the book to remove: ")) - 1
if 0 <= book_index < len(library.books):
removed_book = library.books[book_index]
library.remove_book(removed_book) # Remove the selected book from the library
print(f"{removed_book} has been removed from the library.")
else:
print("Invalid book number. No book removed.")

elif choice == "3":
print("Books in the Library:")
library.list_books() # List the books in the library

elif choice == "4":
print("Goodbye!")
break

else:
print("Invalid choice. Please select a valid option.")
 

Videos for Module 14 - Introducing Object-Oriented Programming

14-1: Introducing Object-Oriented Programming in Python (17:49)

14-2: Object-Oriented Programming Class Methods (2:20)

14-3: Object Oriented Programming: The init special method (4:09)

14-4: Object-Oriented Programming str Special Method (3:54)

14-5: OOP - Using Lists to Manage Objects (7:10)

14-6: S14 Virtual Pet Project and Code Walkthrough (16:38)

14-7: Object-Oriented Programming - Getter and Setter Methods (14:38)

14-8: A14 Object-Oriented Playlist Introduction (1:32)

14-9: OOP Playlist Code Walkthrough 1/8, Creating a Base Class (6:05)

14-10: OOP Playlist Code Walkthrough 2/8, Base Class Setter & Getter (2:09)

14-11: OOP Playlist Code Walkthrough 3/8, Creating a Collection Manager (1:24)

14-12: OOP Playlist Code Walkthrough 4/8, Adding to Collections (2:50)

14-13: OOP Playlist Code Walkthrough 5/8, Removing From Collections (4:36)

14-14: OOP Playlist Code Walkthrough 6/8, Listing Collection Objects (2:16)

14-15: OOP Playlist Code Walkthrough 7/8, Delegation from a Collection (2:53)

14-16: OOP Playlist Code Walkthrough 8/8, Testing Classes (5:37)

Key Terms for Module 14 - Introducing Object-Oriented Programming

No terms have been published for this module.

Quiz Yourself - Module 14 - Introducing Object-Oriented Programming

Test your knowledge of this module by choosing options below. You can keep trying until you get the right answer.

Skip to the Next Question 

Activities for this Module

S14 - E-Pet

Note: Sandbox assignments are designed to be formative activities that are somewhat open-ended. To get the most value, spend some time playing around as you code.

Object-Oriented programming can be a little tricky at first. To get our feet wet, I’ve created a small virtual-pet game that uses OOP to create a class called clPet.  Download the following code and open it in PyCharm:

Download the oop_virtual_pet.py File

If you look at the class definition for clPet, you will see a couple of “special” methods:

__init__( ) defines what happens when an instance of the object is created.  In this case, a few object variables (called “properties”) are created:

  • self.strPetName — This is a string for the pet’s name given as an argument to the instance when it is created.
  • self.intHunger — This is the starting number for the pet’s hunger rating. It will go down by some amount each turn, and when it gets to 0, the pet will run away.
  • self.intBoredom — This is the number for the pet’s boredom.  It works the same way that hunger does.

You may have noticed that the properties of an object start with “self”.  This is to ensure that we are referring to the properties of that instance. If we have 3 pets, each will have their own name, hunger, and boredom.

__ str__( ) is another special method, which changes the default string representation of the object.  Without this, the string representation is set to the type of object it is, and its place in the computer’s memory. With it, when we “print” the object, we get something that can be more useful to our program.

The clPet class also has three other methods defined:

  • takeTurn( ) allows us to trigger a “turn” in the game. It represents the passage of time, during which our pet will get a little more hungry and bored. If you look at the code, you’ll see that the actual amount of each decrease is a randomly chosen number between 0 and 3. A little randomness in a game makes it more interesting. If you change the range, you can adjust the difficulty of the game.
  • feedPet( ) is a simple method that increases the pet’s hunger rating and reflects back to the user that the pet was fed.
  • playPet( ) is another simple method, that does the same thing for boredom that feedPet does for hunger.

Below the class definition, you will see the main program loop, where in each turn, the user gets to choose one of four options to play the game. After they choose, the numbers are adjusted according to their choice and then at the very end the takeTurn( ) method is called for each pet, making sure that our pets are always keeping us on our toes.

For this challenge, read through the “#2” menu option to develop an understanding of how the feedPet( ) method is used.  Then, use this knowledge to complete the “#3” option to play with your pet. Test out your program and see how it goes.  Maybe take a few turns to adjust the difficulty of the game to make it more or less difficult.  For a little extra challenge, see if there is anything else you can do to make the game a little more fun.

A14 - Object-Oriented Playlist

The Challenge

Create a Python program that simulates a music application. The program should allow the user to view, add, remove, or update a playlist. 

Your program should perform the following: 

  1. Create a class named Song that holds the following data about a song: 

• title 
• year 
• rating 

  1. The Song’s __init__ method should accept arguments for the title, year, and rating.
  2. The song class should have methods for updating the rating and printing the info for the song.
  3. Create an array that stores Song objects/instances.
  4. Create functions for adding and removing songs.
  5. The program should prompt the user with a menu having the following options: 

• View songs 
• Add song 
• Remove Song 
• Update rating 
• Exit 

  1. The menu should be prompted after each operation unless the user chooses to exit/quit. 

BONUS CHALLENGE: Use pickle to save and load your playlist data to a text file.

Constraints

NO global usage!

Resources

Submission Type

Please submit the complete program as a .py file.