A Simple Introduction To OOP (Object Oriented Programming)
I still remember the first time I tried to learn programming, my code was absolutely riddled with mistakes, and my attempts to fix the…
A Simple Introduction To OOP (Object Oriented Programming)

image from Pixabay
I still remember the first time I tried to learn programming, my code was absolutely riddled with mistakes, and my attempts to fix the errors were like opening a can of worms. This learning journey reminds me of my first time shopping at the store near my house, — it was terrible experience for many reasons….
First of all: there is no specific section for each product for example there is no certain part for frozen food, meat and seafood, or household goods. Therefore if you are looking for any item (let’s say coffee) you are supposed to look around all the counters.
Second: there were no price tags next to the items, so I had to read a long list to find the price of what I wanted to buy.”
Third: some items in the list that — which I read to know the price — are too sensitive to be written down, for example while I was looking for the coffee’s price, I found a line with info about the total revenue of coffee over the past three months, not only this but when I flipped the list over I found a lot of lines detailing the salary of some workers (salesperson, cashier, sales associate).
Fourth: The list was written in a chaotic style. For instance, it stated there was a 20% discount on coffee. However, at checkout, the cashier informed me this offer only applied to espresso, rather than the cappuccino I had chosen.
Fifth: the price calculation formula has been repeated in the list next to every single item, even though some of them share the same tax, such as food or luxury. Writing prices in this way is a waste of time. If the tax law changes, we would have to manually rewrite the formula for thousands of items!
The price list with the repeated formulas looks like this:
Milk: price + (price * 0.05) (5% Food Tax)
Bread : price +(price * 0.05) (5% Food Tax)
Shampoo: price + (price * 0.18) (18% Luxury Tax)
Perfume : price + (price * 0.18) (18% Luxury Tax)
Soup : price +(price * 0.18) (18% Luxury Tax)
Sixth: the payment method was complicated. You couldn’t just go to the checkout and pay for products; instead, you were supposed to wait in a line based on your type of payment. There were many lines: one for cash, one for credit card, and another for digital wallet/phone. This process frustrated customers who had only one type of payment and were stuck in the wrong line. It also led to operational inflexibility; for example, if the cashier had an empty card terminal but could not assist a long line of cash-paying customers, it resulted in inefficient staffing.
If you ask me if this story is true or if this place really exists, I would say I doubt it, but I am sure that you will experience the same chaos if you write code without following OOP principles
What is OOP:
OOP is a programming paradigm that provide a clear structure to programs, making the code easier to maintain and more organization, it also help at build application with less code.
The core of OOP is class and object. class is the template or blueprint you set to define the attributes and method you need, where as object is the filled template of this class which hold a real values of data and methods.
Classes can help us with data organization by grouping related variables (attributes) and functions (methods) together. This approach offers a perfect solution to the first and second problem :
First problem: — the lack of specific sections for each product — by allowing us to categorize items and customize each product, such as creating specific parts for frozen food or household items.
Second problem: — no price tags beside the items — by structuring products into classes (e.g., Coffee) containing specific attributes: name, price, category, id, and barcode.
The Key of OOP:
Inheritance:
a mechanism that save your time and enable you to write less code by apply the DRY principle (Don’t Repeat Yourself), therefore instead of write the same code multiple times , you can just define a class (parent class ) as a container of all the methods and attributes then let the other classes (child classes) to inherit what they need from it.
Returning to the market example, we can use inheritance to solve the fifth problem (create tax categories) by creating a parent class called Product. then we create child classes for each specific tax rules. The formula is written exactly once in the parent class, and the child classes just provide their specific tax rate.
class Product: # 1. The Parent Class holds the universal calculating formula
def __init__(self, name, base_price):
self.name = name
self.base_price = base_price
self.tax_rate = 0.00 # Default tax is 0% # This is the single method passed to all items
def calculate_final_price(self):
return self.base_price + (self.base_price * self.tax_rate)
class Food(Product):#Child Class Food: inherits the formula, but sets tax to 5%
def __init__(self, name, base_price):
super().__init__(name, base_price)
self.tax_rate = 0.05
class Luxury(Product): # Child Class Luxury: inherits the formula, but sets tax to 18%
def __init__(self, name, base_price):
super().__init__(name, base_price)
self.tax_rate = 0.18
class Beverages(Product): # Child Class Beverages: inherits the formula, but sets tax to 3%
def __init__(self, name, base_price):
super().__init__(name, base_price)
self.tax_rate = 0.03
if __name__ == "__main__":
item1=Food('Meat',8.00)
item2=Food('Fresh Bread',2.00)
item3=Luxury('Designer Perfume',80.00)
item4=Beverages('Coffee',10.00)
print(f"{item1.name} Final Price:${item1.calculate_final_price():.2f}")
print(f"{item2.name} Final Price:${item2.calculate_final_price():.2f}")
print(f"{item3.name} Final Price:${item3.calculate_final_price():.2f}")
print(f"{item4.name} Final price ${item4.calculate_final_price():.2f}")
Output:
Meat Final Price:$8.40
Fresh Bread Final Price:$2.10
Designer Perfume Final Price:$94.40
Coffee Final price $10.30
Abstraction:
The concept of hiding complex implementation details while exposing only the essential features of an object. we focus on what an object does rather than how it does it. There are two main tools to achieve abstraction:
- Abstract Classes: classes that cannot be instantiated directly. They act as a template, defining a common set of methods that derived subclasses must implement, while occasionally providing some concrete, reusable code
- Interfaces: A strict contract containing only method signatures with no implementation details. Any class that implements an interface must provide the specific code for every method
Polymorphism (many forms):
It is the ability of a single interface or name to represent different forms of behavior, there are two main ways to apply that:
- Runtime Polymorphism (Method Overriding): this occurs when a subclass provides its own implementation of a method already defined in its parent class. We define a general method in the parent class; when subclasses inherit it, each one of them overrides the method to perform its own specific task.
- Compile-Time Polymorphism (Method Overloading): this occurs when multiple methods in the same class have the same name but different signatures (different number or types of parameters), we create several versions of a method within one class, with ensuring that each version accepts different arguments.
To capture the real example of this let’s come back to the market visit (sixth problem), instead of have multiple lines for payment process we can have one accepts all payment types like :cash, credit cards, and digital wallets/phone payments
Payment class( parent class) that defines the process payment and then define the following class :
CashPayment (Subclass): Implements process payment by handling cash tender and change.
CreditCardPayment (Subclass): Implements process payment by validating card details and processing through a gateway.
DigitalWalletPayment (Subclass): Implements process payment via phone app integration or QR code.
from abc import ABC, abstractmethod
class Payment(ABC): # abstract class for Payment
@abstractmethod
def process_payment(self, amount: float):
pass
class CashPayment(Payment):
def __init__(self, cash_tendered: float):
self.cash_tendered = cash_tendered
def process_payment(self, amount: float):
if self.cash_tendered < amount:
return f"Error: Insufficient cash. Need ${amount}."
change = self.cash_tendered - amount
print( f"Processed cash payment of ${amount}. Change returned: ${change:.2f}.")
class CreditCardPayment(Payment):
def __init__(self, card_number: str, expiry_date: str, card_verification_value: str):
self.card_number = card_number
self.expiry_date = expiry_date
self.card_verification_value = card_verification_value
def validate_card(self):
return len(self.card_number) == 16 and len(self.card_verification_value) == 3
def process_payment(self, amount: float):
if self.validate_card():
print(f"Processing Credit Card Payment of ${amount:.2f}")
else:
print("Card validation failed.")
class DigitalWalletPayment(Payment):
def __init__(self, wallet_id: str):
self.wallet_id = wallet_id
def process_payment(self, amount: float):
print(f"Initiating QR code for Wallet ID: {self.wallet_id}...")
print(f"Processing digital wallet payment of ${amount:,.2f}.")
if __name__ == "__main__":
# 1. Cash Payment
cash = CashPayment(160.00)
cash.process_payment(90.00)
# 2. Credit Card Payment
card = CreditCardPayment("1234567812345678", "12/28", "123")
card.process_payment(150.00)
# 3. Digital Wallet Payment
wallet = DigitalWalletPayment( "user_phone_123")
wallet.process_payment(320.00)
Output:
Processed cash payment of $90.0. Change returned: $70.00.
Processing Credit Card Payment of $150.00
Initiating QR code for Wallet ID: user_phone_123...
Processing digital wallet payment of $320.00.
Also we can fix fourth issue: ( the 20% discount on coffee, which applies only on espresso, not cappuccino) by creating a Parent class called Coffee that inherit from Beverages with apply_discount() method, and subclasses (Espresso ,Cappuccino ) that inherit from Coffee class.
- Overriding (Espresso): Overrides apply_discount() to set a 20% discount.
- Overriding (Cappuccino): Overrides apply_discount() to set a 0% discount.
class Coffee(Beverages):
def __init__(self,name,base_price):
super().__init__(name,base_price)
self.tax_rate = 0.03
def apply_discount(self):
return self.calculate_final_price()
class Espresso(Coffee):
def apply_discount(self):
return self.calculate_final_price() * 0.80 # Overrides to apply a 20% discount
class Cappuccino (Coffee) :
def apply_discount(self):
return self.calculate_final_price() # Overrides to apply a 0% discount.
if __name__ == "__main__":
espresso= Espresso("Espresso",50.00)
cappuccino=Cappuccino("Cappuccino",30.00)
print(f"{espresso.name} price:${espresso.apply_discount():.2f}")
print(f"{cappuccino.name} price: ${cappuccino.apply_discount():.2f}")
Output:
Espresso price:$41.20
Cappuccino price: $30.90
Encapsulation:
protecting data inside the class by bundling attributes and methods, while controlling how the data is accessed from outside the class.
Python achieves encapsulation through access modifiers, which are naming conventions that indicate how a member should be accessed. There are 3 types of encapsulation in python:
- Public: attributes or methods that can be access anywhere, there is no underscore prefix to it
- Protected: attributes or methods that can be access inside the class and its child classes, denoted by one underscore prefix
- Private: attributes or methods that can be access only inside the class, denoted by double underscore prefix
Encapsulation can be a perfect solution to the third problem by hiding sensitive data: as I am only supposed to see the coffee price, the class only exposes necessary information, keeping sensitive data (revenue) private. Let’s make revenue private by a writing a double underscore __ prefix:
class Coffee(Beverages):
def __init__(self, name, base_price, revenue):
super().__init__(name,base_price)
self.tax_rate = 0.03
self.__revenue = revenue # Private (Encapsulated)
def apply_discount(self):
return self.calculate_final_price()
class Espresso(Coffee):
def apply_discount(self):
return self.calculate_final_price * 0.80
class Cappuccino(Coffee):
def apply_discount(self):
return self.calculate_final_price()
if __name__=="__main__":
espresso = Espresso("Espresso", 50.00, 950)
cappuccino = Cappuccino("Cappuccino", 30.00, 700)
print(f"{espresso.name} revenue: ${espresso.__revenue:.2f}")
print(f"{cappuccino.name} revenue price: ${cappuccino.__revenue:.2f}")
Output :
AttributeError: 'Espresso' object has no attribute '__revenue'
An AttributeError is Raised when we tried to access private attribute (revenue).
If we want to access revenue, we can use a getter method, which only enables you to read private data, not modify it.
class Coffee(Beverages):
def __init__(self, name, base_price, revenue):
super().__init__(name,base_price)
self.tax_rate = 0.03
self.__revenue = revenue # Private (Encapsulated)
def apply_discount(self):
return self.calculate_final_price()
# Getter method to access the private _revenue attribute
def get_revenue(self):
return self.__revenue
class Espresso(Coffee):
def apply_discount(self):
return self.calculate_final_price() * 0.80
class Cappuccino(Coffee):
def apply_discount(self):
return self.calculate_final_price()
if __name__=="__main__":
espresso = Espresso("Espresso", 50.00, 10000)
cappuccino = Cappuccino("Cappuccino", 30.00, 7500)
print(f"{espresso.name} Final_price: {espresso.apply_discount()} revenue: {espresso.get_revenue()}")
print(f"{cappuccino.name} Final_price: {cappuccino.apply_discount()} revenue: {cappuccino.get_revenue()}")
Output:
Espresso Final_price: 41.2 revenue: 10000
Cappuccino Final_price: 30.9 revenue: 7500
In this story, we highlighted the importance of OOP by breaking down its core principles: abstraction, inheritance, polymorphism, and encapsulation with real-world examples. We started by gathering related attributes and methods into one class. Then, we let other classes inherit common features from their parent to avoid repeating the same code. Next, we applied polymorphism by allowing the same method from a parent class to behave differently based on the child class’s logic. Finally, we showed how to keep data protected using encapsulation.
I hope this story serves as a useful guide for learners. If you need further information, please don’t hesitate to ask. Any feedback will be welcome.
메타데이터
- post_id
- b8abd2e06df4
- slug
- a-simple-introduction-to-oop-object-oriented-programming-b8abd2e06df4
- url
- https://medium.com/@engulayousef99/a-simple-introduction-to-oop-object-oriented-programming-b8abd2e06df4
- canonical_url
- https://medium.com/@engulayousef99/a-simple-introduction-to-oop-object-oriented-programming-b8abd2e06df4
- author_url
- https://medium.com/@engulayousef99
- status
- ok
- fetched_at
- 2026-06-09 15:37:30