অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিং প্যারাডাইম
এই পাঠে যা শিখবেন
- OOP প্যারাডাইমের সংজ্ঞা এবং এটি ইম্পারেটিভ প্রোগ্রামিং-এর সাথে ঠিক কীভাবে সম্পর্কিত (L06-এর সরাসরি সম্প্রসারণ)
- OOP-এর চার স্তম্ভ — এনক্যাপসুলেশন, ইনহেরিটেন্স, পলিমরফিজম, অ্যাবস্ট্রাকশন — নির্দিষ্ট সংজ্ঞাসহ
- Python-এ একটি ছোট ক্লাস হায়ারার্কি দিয়ে পলিমরফিজম বাস্তবে কীভাবে কাজ করে তার প্রমাণ
- এই কোর্সের পরবর্তী মডিউলে (M7 টাইপ সিস্টেম, M10 ডেটা অ্যাবস্ট্রাকশন) এই ধারণাগুলো কীভাবে আবার ফিরে আসবে
১ · OOP কী — ইম্পারেটিভ কোড গঠন করার একটি উপায়
অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামিংObject-Oriented Programming (OOP)প্রোগ্রামকে অবজেক্টের চারপাশে সাজানো একটি প্যারাডাইম — প্রতিটি অবজেক্ট তার স্টেট ও বিহেভিয়ার একসাথে বহন করে। প্রোগ্রামকে অবজেক্টের চারপাশে সাজায় — প্রতিটি অবজেক্ট তার নিজের স্টেট (ডেটা/ফিল্ড) এবং সেই স্টেটের ওপর কাজ করা বিহেভিয়ার (মেথড) একসাথে বহন করে। L06-এ আমরা দেখেছি ইম্পারেটিভ প্রোগ্রামিং মিউটেবল ভ্যারিয়েবল ও এক্সপ্লিসিট স্টেট-চেঞ্জ নিয়ে কাজ করে — OOP সেই একই ইম্পারেটিভ-স্টাইল স্টেট-মিউটেশনকে প্রতিস্থাপন করে না, বরং সেটিকে একটি নির্দিষ্ট, গঠনবদ্ধ উপায়ে সংগঠিত করে: প্রতিটি স্টেট এখন কোনো না কোনো অবজেক্টের ভেতরে বাস করে, এবং সেই স্টেট পরিবর্তনের একমাত্র বৈধ পথ হলো সেই অবজেক্টেরই মেথড কল করা।
Shape থেকে দুটি সাবক্লাস — কিন্তু .area() কল করলে
প্রতিটি অবজেক্টের নিজস্ব বাস্তবায়ন স্বয়ংক্রিয়ভাবে কল হয়, কলিং কোডকে জানতেই হয় না কোনটা Rectangle আর কোনটা Circle।২ · চারটি স্তম্ভ
ডেটা ও মেথড একসাথে বেঁধে রাখা, এবং বাইরে থেকে সরাসরি ইন্টারনাল স্টেট অ্যাক্সেস সীমিত রাখা — M10/L46-এর অ্যাবস্ট্রাক্ট ডেটা টাইপের একটি বাস্তবায়ন।
একটি নতুন ক্লাস বিদ্যমান ক্লাসের ভিত্তিতে সংজ্ঞায়িত করা — গঠন ও বিহেভিয়ার পুনর্ব্যবহার/সম্প্রসারণ করা। নিচের কোডে
Rectangle ও Circle দুটোই Shape-এর সন্তান।একই মেথড কল —
shape.area() — অবজেক্টের রানটাইম টাইপ অনুযায়ী ভিন্ন আচরণ দেয়। M7/L35-এ টাইপ-সিস্টেমের দৃষ্টিকোণ থেকে আরও গভীরে যাব।শুধু প্রয়োজনীয় ইন্টারফেস প্রকাশ করা, বাস্তবায়নের খুঁটিনাটি লুকানো — কলিং কোড শুধু জানে
.area() আছে, কীভাবে হিসাব হয় তা জানার দরকার নেই।৩ · পলিমরফিজম বাস্তবে — একটি Shape হায়ারার্কি
নিচের কোডে একটি বেস ক্লাস Shape এবং দুটি সাবক্লাস Rectangle ও Circle —
প্রতিটি নিজস্ব সঠিক সূত্র দিয়ে area() ওভাররাইড করে। দুটো অবজেক্টই একটিমাত্র তালিকায় (shapes)
রাখা হচ্ছে এবং একটিমাত্র অভিন্ন লুপ থেকে .area() কল হচ্ছে — লুপটি জানেও না কোনটা কোন সাবক্লাসের অবজেক্ট,
তবু প্রতিটির জন্য সঠিক সূত্র স্বয়ংক্রিয়ভাবে কল হয়।
import math
class Shape:
"""বেস ক্লাস — area() ঘোষিত কিন্তু বাস্তবায়ন নেই, প্রতিটি সাবক্লাসকে নিজেরটা দিতে হবে"""
def area(self):
raise NotImplementedError("সাবক্লাসকে অবশ্যই 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):
return math.pi * self.radius ** 2
# দুটো ভিন্ন সাবক্লাসের অবজেক্ট একইসাথে একটি তালিকায়
shapes = [Rectangle(3, 4), Circle(2)]
# একটিমাত্র অভিন্ন লুপ -- লুপটি জানেই না কোনটা Rectangle, কোনটা Circle
for shape in shapes:
print(f"{type(shape).__name__}: area = {shape.area():.3f}")
Rectangle(3, 4).area() দেয় 12 (3×4), আর Circle(2).area()
দেয় math.pi * 4 ≈ 12.566 (π×r²)। দুটোই সঠিক, হাতে-যাচাই-করা সূত্র। মূল বিষয়টি হলো: লুপের কোডে
কোথাও if isinstance(shape, Rectangle)-এর মতো কোনো টাইপ-চেক নেই — Python নিজে থেকেই রানটাইমে
প্রতিটি অবজেক্টের সঠিক area() মেথড বেছে নেয়। এটিই পলিমরফিজমের মূল সুবিধা: নতুন একটি
Triangle ক্লাস যোগ করলেও, শুধু area() বাস্তবায়ন করলেই লুপের কোড এক অক্ষরও না
বদলে কাজ করবে।
একটি ক্লাস হায়ারার্কি (Shape → Rectangle/Circle) নিজেই একটি ট্রি
গঠন করে — Data Structures & Algorithms কোর্সের ট্রি ধারণার একটি
বাস্তব প্রয়োগ। এই একই "ট্রি-আকৃতির" চিন্তা পরে M5-এর পার্স ট্রিতেও ফিরে আসবে, যেখানে গ্রামারের নন-টার্মিনাল
ও তাদের সাবক্লাস-সদৃশ এক্সপ্যানশনগুলো একই রকম হায়ারার্কিক্যাল কাঠামো তৈরি করে।
OOP ইম্পারেটিভ প্রোগ্রামিংকে বাতিল করে না — এটি স্টেট ও বিহেভিয়ারকে অবজেক্টের ভেতরে বেঁধে সংগঠিত করার একটি উপায়। চারটি স্তম্ভের মধ্যে পলিমরফিজম বিশেষভাবে গুরুত্বপূর্ণ কারণ এটি কোডকে এক্সটেনসিবল করে — নতুন সাবক্লাস যোগ করা যায় বিদ্যমান কোড না ভেঙে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
উপরের কোডে shapes তালিকার লুপে যদি একটি নতুন Triangle ক্লাস যোগ করা হয় (যেটিও Shape-এর সাবক্লাস এবং নিজস্ব area() বাস্তবায়ন করে), লুপের কোড কি পরিবর্তন করতে হবে?
না — এটিই পলিমরফিজমের সবচেয়ে বড় সুবিধা। লুপটি লেখা আছে for shape in shapes: shape.area()
— এটি কখনোই নির্দিষ্ট কোনো ক্লাসের নাম উল্লেখ করে না, শুধু ধরে নেয় প্রতিটি অবজেক্টের একটি area()
মেথড আছে। যেকোনো নতুন Shape-সাবক্লাস, যতক্ষণ সে area() সঠিকভাবে বাস্তবায়ন করে,
এই একই লুপে বিনা পরিবর্তনে কাজ করবে — এই বৈশিষ্ট্যকে প্রায়ই "ওপেন ফর এক্সটেনশন, ক্লোজড ফর মডিফিকেশন" নীতি বলা হয়।
প্র ০২ এনক্যাপসুলেশন ও অ্যাবস্ট্রাকশন — এই দুটো স্তম্ভ শোনায় প্রায় একই রকম। এদের মধ্যে পার্থক্য কী?
ঘনিষ্ঠভাবে সম্পর্কিত হলেও আলাদা। এনক্যাপসুলেশন হলো বাস্তবায়নের কৌশল — ডেটা ও মেথড একসাথে বেঁধে রাখা এবং সরাসরি বাইরের অ্যাক্সেস সীমিত করা (যেমন Python-এ নামের আগে আন্ডারস্কোর দিয়ে "প্রাইভেট" ইঙ্গিত দেওয়া)। অ্যাবস্ট্রাকশন হলো ডিজাইনের লক্ষ্য — ব্যবহারকারীকে শুধু প্রাসঙ্গিক ইন্টারফেস দেখানো, খুঁটিনাটি লুকানো। এনক্যাপসুলেশন হলো সেই লক্ষ্য অর্জনের একটি হাতিয়ার — আপনি এনক্যাপসুলেশন ব্যবহার করেই অ্যাবস্ট্রাকশন বাস্তবায়ন করেন।
প্র ০৩
Circle(2).area() চালালে ফলাফল ঠিক 12.566370614359172 (পূর্ণ নির্ভুলতায়) — কিন্তু কোড {shape.area():.3f} দিয়ে প্রিন্ট করছে। ফরম্যাট স্ট্রিং-এর :.3f অংশটি কী করছে?
:.3f হলো একটি ফরম্যাট স্পেসিফায়ার যা সংখ্যাটিকে দশমিকের পর ঠিক ৩ ঘর পর্যন্ত রাউন্ড করে দেখায়
(fixed-point notation) — এটি গণনার প্রকৃত নির্ভুলতা পরিবর্তন করে না, শুধু প্রদর্শনের সময় সংক্ষিপ্ত
করে। তাই math.pi * 4-এর প্রকৃত মান 12.566370614359172 থেকেই গণনা হয়, কিন্তু
স্ক্রিনে দেখা যায় 12.566 — পড়ার সুবিধার জন্য, নির্ভুলতা কমানোর জন্য নয়।
অনুশীলন
-
পরীক্ষা করুন: উপরের কোডে
shapesতালিকায় আরেকটিRectangle(5, 5)যোগ করে Run চাপুন — নতুন কোন লাইন প্রিন্ট হবে?নতুন একটি লাইন যোগ হবে:
Rectangle: area = 25.000(যেহেতু 5×5 = 25)। মোট প্রিন্ট হবে তিনটি লাইন — Rectangle 12.000, Circle 12.566, এবং নতুন Rectangle 25.000। লক্ষ্য করুন লুপের কোডে কোনো পরিবর্তন লাগেনি, শুধু ইনপুট তালিকায় একটি নতুন অবজেক্ট যোগ করতে হয়েছে — প্রশ্ন ০১-এর উত্তরের সরাসরি প্রমাণ। -
চিন্তা করুন: যদি
Shape.area()-এraise NotImplementedError(...)-এর বদলে শুধুpass(কিছু না করেNoneরিটার্ন) লেখা থাকত, তাহলে কেউ ভুলবশতarea()ওভাররাইড করতে ভুলে গেলে কী সমস্যা হতো?বাগটি নীরবে (silently) ছড়িয়ে যেত — ভুলে-যাওয়া সাবক্লাসের
.area()কল করলে কোনো এরর ছাড়াইNoneরিটার্ন হতো, এবং সেইNoneপরবর্তী কোনো হিসাবে (যেমন যোগফল) ব্যবহার হলে অনেক পরে, ভিন্ন জায়গায় একটি রহস্যময়TypeErrorদেখা দিত — ডিবাগ করা কঠিন হয়ে যেত।raise NotImplementedErrorব্যবহার করে ভুলটি ঠিক যেখানে ঘটেছে (ওভাররাইড না করা সাবক্লাসে) সেখানেই, সাথে সাথেই, স্পষ্ট এরর মেসেজসহ ধরা পড়ে — একটি ভালো ডিজাইন প্র্যাকটিস।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M2 প্রোগ্রামিং প্যারাডাইম মডিউলের বাকি পাঠগুলো দেখুন — ফাংশনাল, লজিক ও মাল্টি-প্যারাডাইম।
- আগের পাঠ L06 ইম্পারেটিভ প্রোগ্রামিং প্যারাডাইম — OOP যে স্টেট-মিউটেশন-নির্ভর ভিত্তির ওপর দাঁড়িয়ে, সেটাই এখানে ব্যাখ্যা করা হয়েছে।
- পরের পাঠ L08 ফাংশনাল প্রোগ্রামিং প্যারাডাইম — OOP-এর ঠিক বিপরীত দর্শন: মিউটেবল স্টেটের বদলে বিশুদ্ধ ফাংশন ও ইমিউটেবিলিটি।