How is an Object Truly Characterized?
Imagine an active BankAccount object. When querying or interacting with this entity, we ask two fundamental questions:
In concrete software code, these behaviors are implemented as Methods.
01 What is State?
In precise software terminology:
Consider Salim's bank account. At a specific moment in time, its state snapshot looks like this:
Now Salim withdraws $10,000 via an ATM. The withdrawal operation immediately changes the balance attribute. The object enters a newly mutated state:
State is rarely a solitary scalar variable. In production systems, an object's state comprises multiple complementary fields forming a coherent domain concept:
- • username: "salim_dev"
- • email: "salim@byteion.com"
- • status: "ACTIVE"
- • last_login: "2026-09-22 14:20"
- • order_id: "ORD-991"
- • items: ["Keyboard", "Mouse"]
- • total: 4500.00
- • status: "PENDING"
02 What is Behavior?
If State defines what an object knows, then:
An object is not a passive data structure like a CSV row or JSON record. It possesses active capabilities:
- • add_product()
- • remove_product()
- • calculate_total()
- • play()
- • pause()
- • seek_to(seconds)
- • print_document()
- • cancel_job()
- • check_ink_level()
03 Then What is a Method?
In code implementation, Behavior is declared through Methods:
04 Real-World Example & State Mutation: A Car Object
Consider a vehicle (Car) object:
- brand = "Toyota"
- speed = 60 // km/h
- fuel = 40 // liters
- engine_on = True
- accelerate(amount) ➔ increases speed, burns fuel
- brake(amount) ➔ decreases speed
- refuel(liters) ➔ restores fuel
This highlights an essential rule of Object-Oriented software engineering:
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.
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
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:
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:
Methods designed specifically to alter the internal state of the object.
- • deposit(500)
- • set_password("new_pass")
- • cancel_order()
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:
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:
- • battery = 75%
- • volume = 40%
- • screen_locked = True
- • wifi_connected = True
- • 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:
# 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.
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)?