No history yet

Classes and Objects

From Blueprints to Buildings

In programming, you often need to represent real-world things, like users, bank accounts, or even game characters. Before Object-Oriented Programming, you might have stored related data in separate variables and used standalone functions to work on that data. This gets messy quickly. Imagine tracking a user's name, email, and login status with three separate variables, and then writing functions that have to take all three as arguments every single time. It works, but it's not organized.

A class is a blueprint. It defines the structure and behavior for a certain type of thing, but it isn't the thing itself.

Think of an architect's blueprint for a house. The blueprint specifies that the house will have three bedrooms, two bathrooms, and a kitchen. It also defines actions you can perform, like opening a door or turning on a light. The blueprint itself isn't a house you can live in; it's just the plan.

An object is the actual house built from that blueprint. You can build many houses from the same blueprint. Each house is a separate object, with its own unique state, like the color of its walls or the furniture inside. But they all share the same fundamental structure and capabilities defined by the blueprint. In programming, an object is a specific of a class.

Bundling Data and Behavior

A class bundles two things together: data (attributes) and behavior (methods). Attributes are variables that belong to an object, holding its state. Methods are functions that belong to an object, defining what it can do.

Lesson image

Let's create a BankAccount class. Every bank account needs to store a balance and an owner's name. It also needs behaviors like depositing and withdrawing money. Here’s how that blueprint might look in Python:

class BankAccount:
    # This is the constructor method
    def __init__(self, owner_name, starting_balance):
        # These are instance attributes
        self.owner = owner_name
        self.balance = starting_balance
        print(f"Account for {self.owner} created with ${self.balance}.")

    # This is an instance method
    def deposit(self, amount):
        if amount > 0:
            self.balance += amount
            print(f"Deposited ${amount}. New balance: ${self.balance}")

    # Another instance method
    def withdraw(self, amount):
        if 0 < amount <= self.balance:
            self.balance -= amount
            print(f"Withdrew ${amount}. New balance: ${self.balance}")
        else:
            print("Withdrawal failed: insufficient funds or invalid amount.")

Creating and Using Objects

The __init__ method is a special function called a constructors. It runs automatically whenever a new object is created from the class. Its job is to initialize the object's attributes with starting values. The self parameter is a reference to the specific object instance being created, allowing the method to attach data (like self.owner) directly to it.

Now, let's use our BankAccount blueprint to create a couple of actual accounts. This process is called instantiation.

# Instantiating two objects from the BankAccount class
account1 = BankAccount("Alice", 500)
account2 = BankAccount("Bob", 1000)

# > Account for Alice created with $500.
# > Account for Bob created with $1000.

We now have two distinct BankAccount objects: account1 and account2. Each has its own owner and balance. They share the same methods (deposit, withdraw), but the data they operate on is completely separate. When we call a method on an object, we use dot notation.

# Interact with Alice's account
account1.deposit(150)
# > Deposited $150. New balance: $650

# Interact with Bob's account
account2.withdraw(200)
# > Withdrew $200. New balance: $800

# Alice's balance remains unchanged by Bob's actions
print(f"Alice's final balance: ${account1.balance}")
# > Alice's final balance: $650

Notice how calling deposit on account1 only affected account1.balance. The self parameter inside the method automatically refers to whichever object the method was called on. This context is the core of how objects manage their own state.

An object exists in memory as long as it is being used. When there are no more references to it, most modern languages will automatically handle its destruction and free up the memory, a process often managed by a garbage collector.

Quiz Questions 1/6

In object-oriented programming, what is the primary role of a class?

Quiz Questions 2/6

Consider the following Python code:

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

my_car = Car("Red")

What is my_car in this example?