HomeCoursesPython
Chapter 14 of 14

অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং

Classes, objects, inheritance, encapsulation & polymorphism

OOP কী?

Object-Oriented Programming (OOP) হলো একটি programming paradigm যেখানে code-কে objects-এর মাধ্যমে সংগঠিত করা হয়। প্রতিটি object-এর নিজস্ব data (attributes) এবং behavior (methods) থাকে। বাস্তব জগতের জিনিসগুলোকে code-এ মডেল করতে OOP অত্যন্ত কার্যকর।

Procedural vs OOP

বিষয়Procedural ProgrammingObject-Oriented Programming
কেন্দ্রবিন্দুFunctions ও proceduresObjects ও classes
DataFunctions-এর মধ্যে ছড়ানোObject-এর ভেতরে গোপন (encapsulated)
পুনঃব্যবহারFunction copy-pasteInheritance দিয়ে extend
বড় projectজটিল ও অগোছালো হয়ে যায়সুসংগঠিত থাকে
উদাহরণC, early Python scriptsJava, C++, Python classes

বাস্তব জগতের উপমা

একটি গাড়ি (Car) ভাবুন:

🔑 OOP-এর চারটি স্তম্ভ
  • 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
ধারণাব্যাখ্যাউদাহরণ
classBlueprint / নকশাclass Student:
ObjectClass থেকে তৈরি instances1 = Student(...)
__init__()Constructor — object তৈরি হলে চলেAttributes সেট করে
selfবর্তমান object-এর referenceself.name = name
AttributeObject-এর data/বৈশিষ্ট্যself.gpa
MethodObject-এর 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 AttributeClass Attribute
সংজ্ঞায়িত__init__-এর ভেতরে self.x = ...Class body-তে সরাসরি
মানপ্রতিটি object-এ আলাদাসব object-এ একই
Accessself.x বা obj.xClassName.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() = বাবাকে ডাকো!"
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
_ProtectedConvention: বাইরে থেকে access করবেন নাself._salary
__PrivateName 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 — Pythonic Encapsulation
  • @property দিয়ে method-কে attribute-এর মতো access করা যায়
  • @x.setter দিয়ে value সেট করার সময় validation যোগ করা যায়
  • Java/C++-এর getter/setter-এর চেয়ে অনেক পরিষ্কার syntactic sugar
  • acc.balance লিখলেই internally balance() 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 করে)
💡 OOP-এর চার স্তম্ভ মনে রাখার কৌশল

"AEIP" = Abstraction → Encapsulation → Inheritance → Polymorphism
A = জটিলতা লুকাও (Abstract), E = ডেটা সুরক্ষিত রাখো (Encapsulate), I = পুনঃব্যবহার করো (Inherit), P = বহুরূপে কাজ করো (Poly)।
মনে রাখুন: "একটি ইঁদুর পনির খায়"কটি (Abstraction), ঁদুর (Inheritance), নির (Polymorphism) নক্যাপ (Encapsulation)!

🧠 Quick Check
এই অধ্যায়ের মূল ধারণাগুলো যাচাই করুন — ৫টি প্রশ্নের উত্তর দিন:
Q1. Python-এ __init__() method কী কাজ করে?
✅ সঠিক উত্তর: খ) Object তৈরি হওয়ার সময় attributes সেট করে (constructor)__init__() হলো Python-এর constructor যা object instantiate হওয়ার সময় স্বয়ংক্রিয়ভাবে call হয় এবং object-এর প্রাথমিক attributes সেট করে।
Q2. super().__init__() কী কাজ করে?
✅ সঠিক উত্তর: গ) Parent class-এর constructor call করেsuper() parent class-কে reference করে। super().__init__(...) দিয়ে parent-এর __init__ call করা হয় যাতে parent-এর attributes-ও সেট হয়।
Q3. Python-এ self.__balance (double underscore) কী নির্দেশ করে?
✅ সঠিক উত্তর: গ) Private attribute — name mangling হয়, বাইরে সরাসরি access হয় না। Double underscore (__) prefix দিলে Python name mangling করে — self.__balance internally _ClassName__balance হয়ে যায়, তাই বাইরে থেকে obj.__balance দিয়ে access করা যায় না।
Q4. Polymorphism-এর সবচেয়ে ভালো উদাহরণ কোনটি?
✅ সঠিক উত্তর: ক) একই নামের method বিভিন্ন class-এ বিভিন্নভাবে কাজ করা। Polymorphism (বহুরূপতা) মানে একই interface-এ বিভিন্ন behavior। যেমন: DogCat উভয়েরই speak() method আছে কিন্তু ভিন্ন output দেয়।
Q5. Python-এ @property decorator কী কাজে ব্যবহৃত হয়?
✅ সঠিক উত্তর: খ) Method-কে attribute-এর মতো access করতে ও setter validation যোগ করতে@property দিয়ে getter method-কে obj.x syntax-এ access করা যায়। @x.setter দিয়ে value সেট করার সময় validation logic যোগ করা যায় — এটি Pythonic encapsulation।
← Previous: মডিউল ও লাইব্রেরি 🎉 Course Complete — Back to Course Home →