16 min read OOP Foundations Lesson 03 (of 12)
Course 04 · Part 1 — The OOP Mindset & Foundations

State, Behavior & Methods

How data and capabilities live together inside cohesive, self-governing objects

In the previous lesson, we saw that a Class serves as an architectural blueprint while an Object is the concrete living instance allocated in computer memory. But what actually exists inside an object's boundary? How does it store information, execute domain rules, and transition over time? In this lesson, we explore the internal engine of every object: State, Behavior, and Methods.

How is an Object Truly Characterized?

Imagine an active BankAccount object. When querying or interacting with this entity, we ask two fundamental questions:

1. What data or condition does it hold? Whose account is this? What is the account number? What is the current balance? ➔ State
2. What active operations can it execute? Can it deposit funds? Can it withdraw money? Can it transfer balance? ➔ Behavior

In concrete software code, these behaviors are implemented as Methods.

01 What is State?

In precise software terminology:

The State of an object is the complete snapshot of its attributes, data values, and internal condition at any given point in time.

Consider Salim's bank account. At a specific moment in time, its state snapshot looks like this:

// Initial State Snapshot
owner: "Salim"
account_number: "AC-10245"
balance: 50000.00

Now Salim withdraws $10,000 via an ATM. The withdrawal operation immediately changes the balance attribute. The object enters a newly mutated state:

// Mutated State Snapshot
owner: "Salim"
account_number: "AC-10245"
balance: 40000.00 <-- State successfully mutated!

State is rarely a solitary scalar variable. In production systems, an object's state comprises multiple complementary fields forming a coherent domain concept:

👤 User Entity State:
  • • username: "salim_dev"
  • • email: "salim@byteion.com"
  • • status: "ACTIVE"
  • • last_login: "2026-09-22 14:20"
📑 Order Entity State:
  • • order_id: "ORD-991"
  • • items: ["Keyboard", "Mouse"]
  • • total: 4500.00
  • • status: "PENDING"

02 What is Behavior?

If State defines what an object knows, then:

Behavior represents what an Object is capable of doing—its active functions, operations, and reactions to messages.

An object is not a passive data structure like a CSV row or JSON record. It possesses active capabilities:

ShoppingCart
  • • add_product()
  • • remove_product()
  • • calculate_total()
VideoPlayer
  • • play()
  • • pause()
  • • seek_to(seconds)
CloudPrinter
  • • print_document()
  • • cancel_job()
  • • check_ink_level()

03 Then What is a Method?

In code implementation, Behavior is declared through Methods:

A Method is a function declared within the body of a Class that operates on the specific instance's internal state and executes its designated behavior.
The Unified Equation
State = What the object knows (Data)
+
Behavior = What the object can do
➔
Method = Code implementation of behavior

04 Real-World Example & State Mutation: A Car Object

Consider a vehicle (Car) object:

Vehicle State:
  • brand = "Toyota"
  • speed = 60 // km/h
  • fuel = 40 // liters
  • engine_on = True
Vehicle Behavior (Methods):
  • accelerate(amount) ➔ increases speed, burns fuel
  • brake(amount) ➔ decreases speed
  • refuel(liters) ➔ restores fuel

This highlights an essential rule of Object-Oriented software engineering:

Behavior controls, protects, and mutates State.
The State Transition Lifecycle
Current State
speed = 60
fuel = 40
accelerate(20)
New State (Mutated)
speed = 80
fuel = 38

05 Python Implementation & The True Purpose of self

When defining class methods in Python, the first parameter is always explicitly written as self. Why is this necessary?

Because a single class definition can instantiate hundreds of distinct objects in memory. When a method executes, it needs an explicit reference to know: “Which specific object's state am I reading and modifying?” That precise instance reference is passed in as self.

car_engine.py Python 3.x
class Car:
    def __init__(self, brand, initial_fuel=50):
        self.brand = brand          # State
        self.fuel = initial_fuel    # State
        self.speed = 0              # State

    def accelerate(self, increment):
        # Behavior: Domain validation prior to state mutation
        if self.fuel <= 0:
            print(f"[{self.brand}] Out of fuel! Cannot accelerate.")
            return

        self.speed += increment
        self.fuel -= 2
        print(f"[{self.brand}] Accelerated to {self.speed} km/h. Fuel remaining: {self.fuel}L")

    def brake(self, decrement):
        self.speed = max(0, self.speed - decrement)
        print(f"[{self.brand}] Brakes applied. Current speed: {self.speed} km/h")

# Instantiate two independent vehicle objects
car1 = Car("Toyota", initial_fuel=40)
car2 = Car("Tesla", initial_fuel=100)

car1.accelerate(30)  # [Toyota] Accelerated to 30 km/h. Fuel remaining: 38L
car1.brake(10)       # [Toyota] Brakes applied. Current speed: 20 km/h

# car2's state remains completely untouched!
print(f"Tesla speed: {car2.speed}, fuel: {car2.fuel}")  # speed: 0, fuel: 100
Under-The-Hood Mechanics: When you execute car1.accelerate(30), Python's runtime desugars it into: Car.accelerate(car1, 30). The instance car1 is passed as the first argument automatically into self!

06 Enforcing Domain Rules & Business Invariants via Behavior

In loose procedural code, raw dictionaries allow callers to mutate state into catastrophic corruption:

account["balance"] = account["balance"] - 50000 # If balance was 1000, it becomes -49000!

In object-oriented design, the object is the sovereign guardian of its own data. Validation invariants are enforced directly within methods:

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance

    def withdraw(self, amount):
        # Invariant 1: Withdrawal amount must be positive
        if amount <= 0:
            print("Error: Withdrawal amount must be strictly positive.")
            return False

        # Invariant 2: Account must have sufficient balance (No overdraft)
        if amount > self.balance:
            print(f"Denied: Insufficient funds. Available balance: ${self.balance:.2f}")
            return False

        # Mutate state only when all business invariants pass
        self.balance -= amount
        print(f"Withdrew ${amount:.2f}. Remaining balance: ${self.balance:.2f}")
        return True

Here, withdraw() is far more than subtraction; it is an active Domain Gatekeeper guaranteeing that internal account invariants can never be violated.

07 The Dual Nature of Methods: Mutators vs. Accessors (CQS)

Do all methods mutate state? No. In professional software design, methods are broadly classified into two categories:

1. Commands / Mutators (State Altering)

Methods designed specifically to alter the internal state of the object.

  • • deposit(500)
  • • set_password("new_pass")
  • • cancel_order()
2. Queries / Accessors (State Reading)

Methods that compute and return values without producing state-mutating side effects.

  • • get_balance()
  • • is_eligible_for_discount()
  • • calculate_total_tax()

This architectural principle is known as Command-Query Separation (CQS): Asking a question should not change the answer!

08 E-Commerce Case Study: The Order State Machine

In real-world enterprise engineering, domain entities transition across a defined state machine lifecycle:

Order Lifecycle & State Machine Transitions
PENDING
Order created
pay() ➔
PAID
Payment cleared
ship() ➔
SHIPPED
Dispatched to courier
deliver() ➔
DELIVERED
Received by customer

Every method (pay(), ship(), deliver()) advances the entity to its next valid state. Attempting to invoke ship() on a PENDING order will be rejected by the object itself.

09 Real-World Analogy: The Modern Smartphone

The smartphone in your pocket is an intuitive, living physical embodiment of state and behavior:

Device State:
  • • battery = 75%
  • • volume = 40%
  • • screen_locked = True
  • • wifi_connected = True
Device Behavior (Methods):
  • • press_volume_up() ➔ volume: 50%
  • • unlock_with_face() ➔ screen_locked: False
  • • plug_charger() ➔ battery begins charging

10 Engineering Warning: Stuffing Functions into a Class is Not OOP!

A common pitfall occurs when engineers bundle dozens of unrelated operations into a single monstrous class:

class ApplicationManager:
    # User auth, payments, emailing, PDF invoices, and DB backups in ONE class!
    def create_user(self): ...
    def process_payment(self): ...
    def send_email(self): ...
    def backup_database(self): ...

In architecture, this anti-pattern is called the God Object.

The Golden Standard (High Cohesion): An object must only bind state and behaviors that belong naturally together. User profiles belong in User, payment logic belongs in PaymentProcessor. Keep classes focused on a single, clear responsibility.

Concept Verification

Test your understanding of Lesson 03

Question 1: What is the primary purpose of the self parameter in Python class methods?

Question 2: Which method represents an Accessor / Query following Command-Query Separation (CQS)?

Key Takeaways

State: The snapshot of an object's internal variables and condition at a given moment.
Behavior & Methods: What an object does; expressed through class methods that access or mutate instance state.
State Mutation Flow: Invoking methods drives an object across its lifecycle from one valid state to another.
Invariant Guarding: Embedding business validation directly inside methods prevents data corruption.