OOP কী?
Object-Oriented Programming (OOP) হলো একটি programming paradigm যেখানে code-কে objects-এর মাধ্যমে সংগঠিত করা হয়। প্রতিটি object-এর নিজস্ব data (attributes) এবং behavior (methods) থাকে। বাস্তব জগতের জিনিসগুলোকে code-এ মডেল করতে OOP অত্যন্ত কার্যকর।
Procedural vs OOP
| বিষয় | Procedural Programming | Object-Oriented Programming |
|---|---|---|
| কেন্দ্রবিন্দু | Functions ও procedures | Objects ও classes |
| Data | Functions-এর মধ্যে ছড়ানো | Object-এর ভেতরে গোপন (encapsulated) |
| পুনঃব্যবহার | Function copy-paste | Inheritance দিয়ে extend |
| বড় project | জটিল ও অগোছালো হয়ে যায় | সুসংগঠিত থাকে |
| উদাহরণ | C, early Python scripts | Java, C++, Python classes |
বাস্তব জগতের উপমা
একটি গাড়ি (Car) ভাবুন:
- Class = গাড়ির Blueprint (design/নকশা) — সব গাড়ির সাধারণ বৈশিষ্ট্য বর্ণনা করে
- Object = একটি নির্দিষ্ট গাড়ি (যেমন: রহিমের Toyota Corolla) — blueprint থেকে তৈরি
- Attributes = বৈশিষ্ট্য (রঙ, মডেল, মাইলেজ)
- Methods = কাজ (start, drive, brake, stop)
- Encapsulation — data ও methods একসাথে গোপন রাখা
- Abstraction — জটিলতা লুকানো, সহজ interface দেখানো
- Inheritance — parent class থেকে বৈশিষ্ট্য পাওয়া
- Polymorphism — একই method, বিভিন্ন আচরণ
Class ও Object
class keyword দিয়ে class তৈরি করা হয়। __init__() হলো constructor — object তৈরি হওয়ার সময় এটি স্বয়ংক্রিয়ভাবে চলে। self হলো object নিজেকে reference করার উপায়:
# Class তৈরি
class Student:
# Constructor — object তৈরি হলে automatically চলে
def __init__(self, name, roll, gpa):
self.name = name # instance attribute
self.roll = roll
self.gpa = gpa
# Method
def display(self):
print(f"নাম: {self.name}, রোল: {self.roll}, GPA: {self.gpa}")
def is_passed(self):
return self.gpa >= 2.0
# Object তৈরি
s1 = Student("রহিম", 101, 3.75)
s2 = Student("করিম", 102, 1.85)
# Method call
s1.display() # নাম: রহিম, রোল: 101, GPA: 3.75
s2.display() # নাম: করিম, রোল: 102, GPA: 1.85
print(s1.is_passed()) # True
print(s2.is_passed()) # False
# Attribute সরাসরি access
print(s1.name) # রহিম
s1.gpa = 3.80 # attribute পরিবর্তন
print(s1.gpa) # 3.80
| ধারণা | ব্যাখ্যা | উদাহরণ |
|---|---|---|
class | Blueprint / নকশা | class Student: |
| Object | Class থেকে তৈরি instance | s1 = Student(...) |
__init__() | Constructor — object তৈরি হলে চলে | Attributes সেট করে |
self | বর্তমান object-এর reference | self.name = name |
| Attribute | Object-এর data/বৈশিষ্ট্য | self.gpa |
| Method | Object-এর function/কাজ | def display(self): |
Attributes ও Methods
Python-এ দুই ধরনের attribute আছে — Instance attribute (প্রতিটি object-এর আলাদা) এবং Class attribute (সব object-এর জন্য একই):
class Employee:
# Class attribute — সব object-এর জন্য একই
company = "TechBD Ltd."
employee_count = 0
def __init__(self, name, position, salary):
# Instance attributes — প্রতিটি object-এর আলাদা
self.name = name
self.position = position
self.salary = salary
Employee.employee_count += 1 # class attribute update
# Instance method — self দিয়ে object access করে
def get_info(self):
return f"{self.name} - {self.position} ({self.company})"
def annual_salary(self):
return self.salary * 12
# __str__ — print() করলে কী দেখাবে
def __str__(self):
return f"Employee({self.name}, {self.position})"
# __repr__ — developer-friendly representation
def __repr__(self):
return f"Employee('{self.name}', '{self.position}', {self.salary})"
# ব্যবহার
e1 = Employee("রহিম", "Developer", 50000)
e2 = Employee("করিম", "Designer", 45000)
print(e1.get_info()) # রহিম - Developer (TechBD Ltd.)
print(e1.annual_salary()) # 600000
print(e1) # Employee(রহিম, Developer)
# Class attribute — সব object থেকে access যোগ্য
print(Employee.employee_count) # 2
print(e1.company) # TechBD Ltd.
| বিষয় | Instance Attribute | Class Attribute |
|---|---|---|
| সংজ্ঞায়িত | __init__-এর ভেতরে self.x = ... | Class body-তে সরাসরি |
| মান | প্রতিটি object-এ আলাদা | সব object-এ একই |
| Access | self.x বা obj.x | ClassName.x বা obj.x |
| উদাহরণ | নাম, বয়স, বেতন | কোম্পানির নাম, মোট সংখ্যা |
Inheritance (উত্তরাধিকার)
Inheritance দিয়ে একটি class (child) অন্য class (parent) থেকে attributes ও methods উত্তরাধিকার সূত্রে পায়। এটি code reuse-এর সবচেয়ে শক্তিশালী উপায়:
# Parent class (Base class)
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
def speak(self):
return f"{self.name} শব্দ করছে"
def info(self):
return f"{self.name} হলো একটি {self.species}"
# Child class (Derived class)
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name, "কুকুর") # parent-এর __init__ call
self.breed = breed # নতুন attribute
# Method overriding — parent-এর method নতুনভাবে লেখা
def speak(self):
return f"{self.name} বলছে: ঘেউ ঘেউ! 🐕"
# নতুন method
def fetch(self, item):
return f"{self.name} {item} নিয়ে আসছে!"
class Cat(Animal):
def __init__(self, name, color):
super().__init__(name, "বিড়াল")
self.color = color
def speak(self):
return f"{self.name} বলছে: মিয়াউ! 🐱"
# ব্যবহার
dog = Dog("টমি", "জার্মান শেফার্ড")
cat = Cat("মিটু", "সাদা")
print(dog.info()) # টমি হলো একটি কুকুর (parent-এর method)
print(dog.speak()) # টমি বলছে: ঘেউ ঘেউ! 🐕 (overridden)
print(dog.fetch("বল")) # টমি বল নিয়ে আসছে!
print(cat.info()) # মিটু হলো একটি বিড়াল
print(cat.speak()) # মিটু বলছে: মিয়াউ! 🐱
"super() = বাবাকে ডাকো!"
super().__init__(...) মানে parent class-এর constructor-কে call করা। এটি ছাড়া parent-এর attributes সেট হবে না!
নিয়ম: Child class-এর __init__-এ সবসময় super().__init__(...) call করুন (যদি parent-এর __init__ থাকে)।
Encapsulation
Encapsulation হলো data-কে বাইরে থেকে সরাসরি access বন্ধ করা এবং methods-এর মাধ্যমে নিয়ন্ত্রিত access দেওয়া। Python-এ naming convention দিয়ে encapsulation করা হয়:
| Prefix | নাম | Access Level | উদাহরণ |
|---|---|---|---|
| (কিছু না) | Public | সবাই access করতে পারে | self.name |
_ | Protected | Convention: বাইরে থেকে access করবেন না | self._salary |
__ | Private | Name mangling — বাইরে সরাসরি access হয় না | self.__password |
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner # public
self._account_type = "Savings" # protected (convention)
self.__balance = balance # private (name mangling)
# Getter — balance পড়ার জন্য
@property
def balance(self):
return self.__balance
# Setter — balance সেট করার জন্য (validation সহ)
@balance.setter
def balance(self, amount):
if amount < 0:
raise ValueError("ব্যালেন্স নেতিবাচক হতে পারে না!")
self.__balance = amount
def deposit(self, amount):
if amount <= 0:
raise ValueError("পরিমাণ ধনাত্মক হতে হবে!")
self.__balance += amount
return f"৳{amount} জমা হয়েছে। বর্তমান ব্যালেন্স: ৳{self.__balance}"
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("অপর্যাপ্ত ব্যালেন্স!")
self.__balance -= amount
return f"৳{amount} তোলা হয়েছে। বর্তমান ব্যালেন্স: ৳{self.__balance}"
# ব্যবহার
acc = BankAccount("রহিম", 5000)
print(acc.owner) # রহিম (public — OK)
print(acc.balance) # 5000 (@property দিয়ে access)
print(acc.deposit(3000)) # ৳3000 জমা হয়েছে। বর্তমান ব্যালেন্স: ৳8000
print(acc.withdraw(2000)) # ৳2000 তোলা হয়েছে। বর্তমান ব্যালেন্স: ৳6000
# Private attribute সরাসরি access করা যায় না
# print(acc.__balance) # AttributeError!
# @property setter দিয়ে validation
acc.balance = 10000 # OK
# acc.balance = -500 # ValueError: ব্যালেন্স নেতিবাচক হতে পারে না!
@propertyদিয়ে method-কে attribute-এর মতো access করা যায়@x.setterদিয়ে value সেট করার সময় validation যোগ করা যায়- Java/C++-এর getter/setter-এর চেয়ে অনেক পরিষ্কার syntactic sugar
acc.balanceলিখলেই internallybalance()method চলে
Polymorphism
Polymorphism (বহুরূপতা) মানে একই নামের method বিভিন্ন class-এ বিভিন্নভাবে আচরণ করা। Python-এ এটি method overriding এবং duck typing দিয়ে কাজ করে:
# Method Overriding দিয়ে Polymorphism
class Shape:
def area(self):
raise NotImplementedError("Child class-এ area() implement করুন!")
def describe(self):
return f"{self.__class__.__name__}: ক্ষেত্রফল = {self.area()}"
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
import math
return round(math.pi * self.radius ** 2, 2)
class Triangle(Shape):
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return 0.5 * self.base * self.height
# Polymorphism in action!
shapes = [
Rectangle(10, 5),
Circle(7),
Triangle(8, 6)
]
for shape in shapes:
print(shape.describe())
# Rectangle: ক্ষেত্রফল = 50
# Circle: ক্ষেত্রফল = 153.94
# Triangle: ক্ষেত্রফল = 24.0
Duck Typing
Python-এর দর্শন: "If it walks like a duck and quacks like a duck, it must be a duck." — অর্থাৎ, object কোন class-এর সেটা গুরুত্বপূর্ণ নয়, সে কী করতে পারে সেটাই গুরুত্বপূর্ণ:
# Duck Typing — class কোনো ব্যাপার না, method থাকলেই হলো!
class Duck:
def sound(self):
return "কোয়াক কোয়াক!"
class Dog:
def sound(self):
return "ঘেউ ঘেউ!"
class Car:
def sound(self):
return "ভ্রুম ভ্রুম!"
# সবাই sound() করতে পারে — class যাই হোক
def make_sound(thing):
print(thing.sound())
make_sound(Duck()) # কোয়াক কোয়াক!
make_sound(Dog()) # ঘেউ ঘেউ!
make_sound(Car()) # ভ্রুম ভ্রুম!
# isinstance() — object এর type পরীক্ষা
print(isinstance(Duck(), Duck)) # True
print(isinstance(Dog(), Animal)) # True (যদি Dog, Animal inherit করে)
"AEIP" = Abstraction → Encapsulation → Inheritance → Polymorphism
A = জটিলতা লুকাও (Abstract), E = ডেটা সুরক্ষিত রাখো (Encapsulate), I = পুনঃব্যবহার করো (Inherit), P = বহুরূপে কাজ করো (Poly)।
মনে রাখুন: "একটি ইঁদুর পনির খায়" — একটি (Abstraction), ইঁদুর (Inheritance), পনির (Polymorphism) এনক্যাপ (Encapsulation)!