Python Object Oriented Programming- Message Passing

Message passing is a way to request a unit of code engage in a behaviour, i.e. changing its state, or sharing some aspect of its state.

Consider the real-world analogue of a letter sent via the postal service. Such a message consists of: an address the message needs to be sent to, a return address, the message itself (the letter), and any data that needs to accompany the letter (the enclosures). A specific letter might be a wedding invitation. The message includes the details of the wedding (the host, the location, the time), The recipient should (per custom) send a response to the host addressed to the return address letting them know if they will be attending.

In Python Object-Oriented Programming (OOP) Message passing refers to the process of objects interacting with one another by calling methods (or functions) on each other. It is a core concept in OOP, where objects send and receive messages, which are essentially method calls. In Python, when you invoke a method on an object, you are passing a "message" to that object, asking it to perform an action or return a value.

Key Points:

1. Objects as Receivers of Messages: Objects encapsulate data (attributes) and behaviour (methods). When one object calls a method of another object, it sends a message to that object.

2. Methods as Handlers of Messages: Methods define how an object responds to a specific message. The method’s implementation processes the message and performs the desired action.

3. Dynamic Behaviour: Message passing allows objects to interact in a dynamic way. The exact method that gets called depends on the object receiving the message, supporting polymorphism.

Example:

Let’s look at a simple example where different objects respond to a common message:

class Dog:
   
def speak(self):
       
return "Woof!"

class Cat:
   
def speak(self):
       
return "Meow!"

def make_animal_speak(animal):
   
return animal.speak()

Objects of different types

dog = Dog()
cat = Cat()

Passing the same
'message' to different objects

print(make_animal_speak(dog))  
# Outputs: Woof!
print(make_animal_speak(cat))  
# Outputs: Meow!

Message Passing Works in this way:

The `make_animal_speak` function sends the "speak" message to the `dog` and `cat` objects.

Each object has a different implementation of the `speak` method.

The behaviour (the returned sound) changes depending on the object that receives the message.

Advantages:

Encapsulation: Objects encapsulate behaviour, and message passing allows them to interact without exposing their internal states.

Polymorphism: Different objects can respond to the same message in different ways, leading to flexible code.

Abstraction: Message passing hides the details of how an object handles a particular request, promoting abstraction.

Message passing is thus a core concept in object-oriented design that allows different objects to work together while maintaining clean, modular code. Now let us discuss the key points with numerous examples on each point to clear the concept of Message passing in Python Object Oriented Programming.

Objects as Receivers of Messages  

Here are some examples of Objects as receivers of messages.

1. Bank Account Example

In this example, a BankAccount object encapsulates data like balance and behaviour like depositing or withdrawing money. The object receives messages when methods are called on it, and it responds by performing the action (deposit/withdrawal).

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

   
def deposit(self, amount):
       
self.balance += amount
       
return f"Deposited {amount}. New balance is {self.balance}"   

   
def withdraw(self, amount):
       
if self.balance >= amount:
           
self.balance -= amount
           
return f"Withdrew {amount}. New balance is {self.balance}"
       
return "Insufficient funds"

#Object instances

account = BankAccount(
100)

#Messages sent to the object

print(account.deposit(
50))  # Depositing money (message)
print(account.withdraw(
30)) # Withdrawing money (message)

Explanation:

  • account.deposit(50) sends a "deposit" message to the account object, asking it to add the specified amount to its balance.
  • account.withdraw(30) sends a "withdraw" message, and the object responds accordingly by deducting the amount.

2. Car Object Example

A Car object encapsulates its state (like speed) and behaviors (like accelerating or braking). When messages are sent, the car adjusts its state.

class Car:
   
def __init__(self, speed=0):
       
self.speed = speed

   
def accelerate(self, increment):
       
self.speed += increment
       
return f"Car accelerated to {self.speed} mph."

   
def brake(self, decrement):
       
self.speed -= decrement
       
return f"Car slowed down to {self.speed} mph."

# Object instances

car = Car()

# Sending messages

print(car.accelerate(
30))  # Speeding up (message)
print(car.brake(
10))       # Slowing down (message)

Explanation:

car.accelerate(30) sends an "accelerate" message to the car object, increasing its speed by 30 mph.

car.brake(10) sends a "brake" message, reducing the speed by 10 mph.

3. Smart Light Example

Here, a SmartLight object represents a smart light with methods for turning on, turning off, and changing its brightness.

class SmartLight:

   
def __init__(self, brightness=0):
       
self.brightness = brightness
       
self.is_on = False

   

def turn_on(self):
       
self.is_on = True
       
return "Light is on."

   
def turn_off(self):
       
self.is_on = False
       
return "Light is off."   

   
def set_brightness(self, level):
       
self.brightness = level
       
return f"Brightness set to {self.brightness}."

# Object instances

light = SmartLight()

# Sending messages

print(light.turn_on())            
# Turn on light (message)
print(light.set_brightness(
75))    # Change brightness (message)
print(light.turn_off())            
# Turn off light (message)

Explanation:

light.turn_on() sends a "turn_on" message to the light object, which turns the light on.

light.set_brightness(75) sends a "set_brightness" message to adjust its brightness.

light.turn_off() sends a "turn_off" message, which turns off the light.

4. Music Player Example

A MusicPlayer object encapsulates behaviour related to playing, pausing, and stopping music.

class MusicPlayer:

   
def __init__(self):
       
self.is_playing = False
   

   
def play(self, song):
       
self.is_playing = True
       
return f"Playing {song}"

   

def pause(self):
       
if self.is_playing:
           self.is_playing = False
           
return "Music paused"
       
return "No music playing"   

def
stop(self):
       
if self.is_playing:
           self.is_playing = False
           
return "Music stopped"
       
return "No music playing"

# Object instance

player = MusicPlayer()

# Sending messages

print(player.play(
"Song A"))  # Start playing (message)
print(player.pause())        
# Pause music (message)
print(player.
stop())          # Stop music (message)
Explanation:
player.play(
"Song A") sends a "play" message to the player object, asking it to play a song.
player.pause() sends
a "pause" message, causing the object to pause the current music.
player.
stop() sends a "stop" message, instructing the player to stop the music.

5. E-commerce Cart Example

In an e-commerce application, a ShoppingCart object encapsulates a list of items and provides behaviours like adding or removing items.

class ShoppingCart:
   
def __init__(self):
       self.items = []    

   
def add_item(self, item):
       self.items.append(item)
       
return f"Added {item} to cart."   

   
def remove_item(self, item):
       
if item in self.items:
           self.items.remove(item)
           
return f"Removed {item} from cart."
       
return f"{item} not in cart."

   
def view_cart(self):
       
return f"Items in cart: {', '.join(self.items) if self.items else 'No items'}"

# Object instance

cart = ShoppingCart()

# Sending messages

print(cart.add_item(
"Laptop"))  # Add item (message)
print(cart.add_item(
"Phone"))   # Add another item (message)
print(cart.view_cart())        
# View cart contents (message)

Explanation:

  • cart.add_item("Laptop") sends an "add_item" message to the cart object, asking it to add a laptop to the cart.
  • cart.remove_item("Phone") sends a "remove_item" message, which causes the cart to remove the specified item.
  • cart.view_cart() sends a "view_cart" message to display the current contents of the shopping cart.

In each example, the object encapsulates its own data (attributes) and behaviors (methods). When you call a method on an object, you're sending a message to that object, asking it to perform an action based on its internal state. This interaction is a fundamental concept of OOP in Python, enabling objects to communicate in a modular, flexible way.

Methods as Handlers of Messages:

1. Vending Machine Example

A `VendingMachine` object has methods to handle user actions such as inserting coins, selecting items, and dispensing the product. Each method processes a specific message.

class VendingMachine:
   
def __init__(self):
       
self.balance = 0
       
self.products = {"Soda": 25, "Chips": 15, "Candy": 10}

   
def insert_coin(self, amount):
       
self.balance += amount
       
return f"Inserted ${amount}. Current balance is ${self.balance}."

   
def select_product(self, product):
       
if product in self.products and self.balance >= self.products[product]:
           
self.balance -= self.products[product]
           
return f"Dispensing {product}. Remaining balance is ${self.balance}."
       elif product
not in self.products:
           
return f"{product} is not available."
       
else:
           
return f"Not enough balance for {product}."

   
def refund(self):
       refund_amount =
self.balance
       
self.balance = 0
       
return f"Refunded ${refund_amount}."

Object instance
vending_machine = VendingMachine()

Sending messages
and methods responding

print(vending_machine.insert_coin(
30))       # Insert coin (message)
print(vending_machine.select_product(
"Soda")) # Select product (message)
print(vending_machine.refund())              
# Refund balance (message)

Explanation:

Method `insert_coin()’ handles the message of inserting a coin by updating the balance.

Method `select_product()`processes the message of product selection by checking if enough balance exists and dispensing the product.

Method `refund()`responds by returning the remaining balance to the user.

2. ATM Machine Example

An `ATM` object has methods to handle operations like checking balance, depositing, and withdrawing money. Each method responds to user messages (requests).

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

   
def deposit(self, amount):
       
self.balance += amount
       
return f"Deposited ${amount}. New balance is ${self.balance}."

   
def withdraw(self, amount):
       
if self.balance >= amount:
           
self.balance -= amount
           
return f"Withdrew ${amount}. New balance is ${self.balance}."
       
return "Insufficient balance."

   
def check_balance(self):
       
return f"Current balance is ${self.balance}."

Object instance
atm = ATM(
100)

Sending messages
and methods responding

print(atm.deposit(
50))        # Deposit money (message)
print(atm.withdraw(
30))       # Withdraw money (message)
print(atm.check_balance())    
# Check balance (message)



Explanation:

Method `deposit()` handles the deposit message by increasing the balance.

Method `withdraw()` processes the withdrawal request and adjusts the balance if sufficient funds exist.

Method `check_balance()` responds by showing the current balance.

3. Thermostat Example

A `Thermostat` object controls room temperature and responds to messages like setting the temperature, increasing or decreasing it, and showing the current temperature.

class Thermostat:
   
def __init__(self, temperature=20):
       
self.temperature = temperature

   
def set_temperature(self, new_temp):
       
self.temperature = new_temp
       
return f"Temperature set to {self.temperature}°C."

   
def increase_temperature(self, increment):
       
self.temperature += increment
       
return f"Increased temperature to {self.temperature}°C."

   
def decrease_temperature(self, decrement):
       
self.temperature -= decrement
       
return f"Decreased temperature to {self.temperature}°C."

   
def show_temperature(self):
       
return f"Current temperature is {self.temperature}°C."

Object instance
thermostat = Thermostat()

Sending messages
and methods responding
print(thermostat.set_temperature(
22))  # Set temperature (message)
print(thermostat.increase_temperature(
3)) # Increase temperature (message)
print(thermostat.decrease_temperature(
2)) # Decrease temperature (message)
print(thermostat.show_temperature())  
# Show current temperature (message)

Explanation:

Method `set_temperature()` handles the message of setting a specific temperature.

Method `increase_temperature()` responds by increasing the temperature by a

specified value.

Method `decrease_temperature()` processes the message by reducing the temperature.

Method `show_temperature()` responds by showing the current temperature.

4. Library System Example

In this example, a `Library` object has methods to handle checking out, returning, and searching for books.

class Library:
   
def __init__(self):
       self.books = {
"Python 101": 3, "Data Science": 2, "AI Basics": 1}

   
def check_out(self, book):
       
if book in self.books and self.books[book] > 0:
           self.books[book] -=
1
           
return f"Checked out {book}. Remaining copies: {self.books[book]}"
       
elif book not in self.books:
           
return f"{book} is not available in the library."
       
else:
           
return f"{book} is out of stock."

   
def return_book(self, book):
       
if book in self.books:
           self.books[book] +=
1
           
return f"Returned {book}. Available copies: {self.books[book]}"
       
return f"{book} does not belong to this library."

   
def search_book(self, book):
       
if book in self.books:
           
return f"{book} is available with {self.books[book]} copies."
       
return f"{book} is not available."

Object instance

library = Library()

Sending messages
and methods responding
print(library.check_out(
"Python 101"))  # Check out a book (message)
print(library.return_book(
"Python 101")) # Return a book (message)

print(library.search_book("AI Basics"))  # Search for a book (message)

Explanation:

Method `check_out()`responds to the message by reducing the number of available copies of a book.

Method `return_book()` processes the return message and increases the number of available copies.

Method `search_book()` handles the search message by providing information about book availability.

5. Online Order System Example

An `Order` object represents an online order system where methods handle actions like adding products, removing them, and calculating the total.

class Order:
   
def __init__(self):
       
self.items = {}
   
   
def add_item(self, item, price):
       
if item in self.items:
           
self.items[item] += 1
       
else:
           
self.items[item] = 1
       
return f"Added {item} to cart. Total {self.items[item]}."
   
   
def remove_item(self, item):
       
if item in self.items:
           
if self.items[item] > 1:
               
self.items[item] -= 1
           
else:
               del
self.items[item]
           
return f"Removed {item} from cart."
       
return f"{item} not in cart."
   
   
def total_price(self):
       
return f"Total price: ${sum(self.items[item] * price for item, price in self.items.items())}"

Object instance
order = Order()

Sending messages
and methods responding
print(order.add_item("Laptop", 800))  # Add item to order (message)
print(order.add_item("Mouse", 50))    # Add another item (message)
print(order.remove_item("Mouse"))     # Remove item from order (message)
print(order.total_price())            # Get total price (message)

Explanation:

Method `add_item()` handles the message of adding an item to the order.

Method `remove_item()` responds to the message by removing an item from the cart.

Method `total_price()` processes the message by calculating the total price of the items in the cart.

In each of these examples, methods act as handlers that process incoming messages (method calls) and perform specific actions. The object receives the message, and the corresponding method interprets it to update the state, perform calculations, or return a response. This encapsulation of logic and response to messages is central to the behaviour of objects in Python's Object-Oriented Programming (OOP).

Dynamic behaviour and polymorphism through message passing in Python.

Each example demonstrates how different objects can respond to the same message in different ways, depending on the object receiving the message.

1. Animal Sound Example (Polymorphism)

In this example, different animal objects respond to the same "speak" message differently based on their type.

class Animal:
   
def speak(self):
       
raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
   
def speak(self):
       
return "Woof!"

class Cat(Animal):
   
def speak(self):
       
return "Meow!"

class Cow(Animal):
   
def speak(self):
       
return "Moo!"

# Function that handles polymorphism

def animal_speak(animal):
   
return animal.speak()

# Object instances

dog = Dog()
cat = Cat()
cow = Cow()

# Sending the same message to different objects

print(animal_speak(dog))  
# Outputs: Woof!
print(animal_speak(cat))  
# Outputs: Meow!
print(animal_speak(cow))  
# Outputs: Moo!xplanation:

  • The same message, speak(), is passed to different objects (Dog, Cat, Cow).
  • Each object has its own version of the speak() method, so the response depends on the object receiving the message.
  • This demonstrates polymorphism, where the exact method that gets called depends on the object's type.

2. Shape Area Example (Polymorphism)

In this example, different shape objects (circle, rectangle, square) respond to the same message "area" and return different calculations based on their shape.

class Shape:
   
def area(self):
       raise NotImplementedError(
"Subclass must implement abstract method")

class Circle(Shape):

   
def __init__(self, radius):
       
self.radius = radius
   
def area(self):
       
return 3.14 * self.radius ** 2

class Rectangle(Shape):

   
def __init__(self, length, width):
       
self.length = length
       
self.width = width
   
def area(self):
       
return self.length * self.width

class Square(Shape):
   
def __init__(self, side):
       
self.side = side
   
def area(self):
       
return self.side ** 2

# Function that handles polymorphism

def calculate_area(shape):
   
return shape.area()

class Shape:
   
def area(self):
       raise NotImplementedError(
"Subclass must implement abstract method")

class Circle(Shape):

   
def __init__(self, radius):
       
self.radius = radius
   
def area(self):
       
return 3.14 * self.radius ** 2

class Rectangle(Shape):

   
def __init__(self, length, width):
       
self.length = length
       
self.width = width
   
def area(self):
       
return self.length * self.width

class Square(Shape):
   
def __init__(self, side):
       
self.side = side
   
def area(self):
       
return self.side ** 2

# Function that handles polymorphism

def calculate_area(shape):
   
return shape.area()

Explanation:

The same message, area(), is passed to different shape objects (Circle, Rectangle, Square).

Each shape object calculates its area differently based on its type, demonstrating dynamic behaviour and polymorphism.

3. Payment System Example (Polymorphism)

Different payment methods like credit card, PayPal, and bank transfer respond to the same message "pay" in different ways based on the method chosen.

class PaymentMethod:

   
def pay(self, amount):
       
raise NotImplementedError("Subclass must implement abstract method")

class CreditCard(PaymentMethod):
   
def pay(self, amount):
       
return f"Paid ${amount} using Credit Card."

class PayPal(PaymentMethod):
   
def pay(self, amount):
       
return f"Paid ${amount} using PayPal."

class BankTransfer(PaymentMethod):
   
def pay(self, amount):
       
return f"Paid ${amount} using Bank Transfer."

# Function that handles polymorphism

def process_payment(payment_method, amount):
   
return payment_method.pay(amount)

# Object instances

credit_card = CreditCard()
paypal = PayPal()
bank_transfer = BankTransfer()

# Sending the same message to different objects

print(process_payment(credit_card,
100))    # Outputs: Paid $100 using Credit Card.
print(process_payment(paypal,
200))         # Outputs: Paid $200 using PayPal.
print(process_payment(bank_transfer,
300))  # Outputs: Paid $300 using Bank Transfer.

Explanation:

The message pay(amount) is passed to different payment method objects (CreditCard, PayPal, BankTransfer).

Each payment method processes the payment in its own way, showing dynamic behaviour and polymorphism.

4. Document Rendering Example (Polymorphism)

Different document types (PDF, Word, and HTML) respond to the same message "render" in different ways depending on their format.

class Document:
   
def render(self):
       raise NotImplementedError(
"Subclass must implement abstract method")

class PDF(Document):
   
def render(self):
       
return "Rendering PDF document."

class Word(Document):
   
def render(self):
       
return "Rendering Word document."

class HTML(Document):
   
def render(self):
       
return "Rendering HTML document."

# Function that handles polymorphism

def render_document(doc):
   
return doc.render()

# Object instances

pdf_doc = PDF()
word_doc = Word()
html_doc = HTML()

class Document:
   
def render(self):
       raise NotImplementedError(
"Subclass must implement abstract method")

class PDF(Document):
   
def render(self):
       
return "Rendering PDF document."

class Word(Document):
   
def render(self):
       
return "Rendering Word document."

class HTML(Document):
   
def render(self):
       
return "Rendering HTML document."

# Function that handles polymorphism

def render_document(doc):
   
return doc.render()

# Object instances

pdf_doc = PDF()
word_doc = Word()
html_doc = HTML()

Explanation:

  • The message render() is passed to different document objects (PDF, Word, HTML).
  • Each document type responds in its own way based on its format, demonstrating dynamic behaviour and polymorphism.

5. Vehicle Fuel Example (Polymorphism)

In this example, different vehicle types (car, bike, and truck) respond to the same "fuel_consumption" message differently based on the type of vehicle.

class Vehicle:
   
def fuel_consumption(self):
       raise NotImplementedError(
"Subclass must implement abstract method")

class Car(Vehicle):
   
def fuel_consumption(self):
       
return "Car consumes 8 liters per 100 km."

class Bike(Vehicle):
   
def fuel_consumption(self):
       
return "Bike consumes 3 liters per 100 km."

class Truck(Vehicle):
   
def fuel_consumption(self):
       
return "Truck consumes 15 liters per 100 km."

# Function that handles polymorphism

def calculate_fuel(vehicle):
   
return vehicle.fuel_consumption()

# Object instances

car = Car()
bike = Bike()
truck = Truck()

# Sending the same message to different objects

print(calculate_fuel(car))
# Outputs: Car consumes 8 liters per 100 km.
print(calculate_fuel(bike))
# Outputs: Bike consumes 3 liters per 100 km.
print(calculate_fuel(truck))
# Outputs: Truck consumes 15 liters per 100 km.

Explanation:

The message fuel_consumption() is passed to different vehicle objects (Car, Bike, Truck).

Each vehicle type responds with its specific fuel consumption rate, illustrating dynamic behaviour and polymorphism

In each example, different objects respond to the same message in a way that's specific to their type, demonstrating polymorphism. This dynamic behaviour allows objects of different types to be treated uniformly (e.g., by calling the same method), while still responding in a way that suits their internal implementation. This is a fundamental feature of object-oriented programming and is essential for writing flexible and scalable code.

No comments:

Post a Comment