পাঠ ৪৬ · ৫৮-এর মধ্যে · মডিউল ১০
Home / Courses / Concepts of Programming Languages & Compiler Design / অ্যাবস্ট্রাক্ট ডেটা টাইপ

অ্যাবস্ট্রাক্ট ডেটা টাইপ ও এনক্যাপসুলেশন

Abstract data types & encapsulation
৮ মিনিট পড়া মধ্যবর্তী · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • অ্যাবস্ট্রাক্ট ডেটা টাইপ (ADT) — আচরণ-ভিত্তিক সংজ্ঞা, এবং M2/L07-এর OOP এনক্যাপসুলেশনের সাথে সম্পর্ক
  • ইনফরমেশন হাইডিং — কেন ইন্টারনাল রিপ্রেজেন্টেশন লুকানো ক্লায়েন্ট কোডকে ইমপ্লিমেন্টেশন থেকে স্বাধীন করে
  • ইনভেরিয়েন্ট — একটি ADT-এর অপারেশন সবসময় যা বজায় রাখতে গ্যারান্টি দেয়
  • Python দিয়ে একটি SortedListADT বাস্তবায়ন, যেখানে ইনভেরিয়েন্ট প্রতিটি অপারেশনের পর এক্সপ্লিসিটভাবে যাচাই করা হয়

১ · অ্যাবস্ট্রাক্ট ডেটা টাইপ — আচরণ দিয়ে সংজ্ঞায়িত, রিপ্রেজেন্টেশন দিয়ে নয়

অ্যাবস্ট্রাক্ট ডেটা টাইপAbstract Data Type (ADT)একটি ডেটা টাইপ, যা সংজ্ঞায়িত হয় তার সাপোর্টেড অপারেশন ও সেগুলোর গ্যারান্টি দিয়ে — ইন্টারনাল রিপ্রেজেন্টেশন দিয়ে নয়। হলো এমন একটি ডেটা টাইপ, যা সংজ্ঞায়িত হয় তার আচরণ (কী কী অপারেশন সাপোর্ট করে, এবং সেগুলো কী গ্যারান্টি দেয়) দিয়ে — ইন্টারনাল রিপ্রেজেন্টেশন দিয়ে নয়। এটি M2/L07-এর OOP এনক্যাপসুলেশন পিলারের একটি সাধারণীকরণ/ফরমালাইজেশন — সেই সম্পর্কটা এক্সপ্লিসিটভাবে বলা দরকার: OOP ক্লাস হলো একটি ADT বাস্তবায়নের একটি সাধারণ (কিন্তু একমাত্র নয়) মেকানিজম। সরাসরি ক্রস-রেফারেন্স — ../dsa/-এ stack/queue/list-এর মতো ADT মূলত অ্যালগরিদমিক আচরণের দৃষ্টিকোণ থেকে পড়ানো হয়েছে; এই পাঠ বরং ভাষা-মেকানিজম-এর দৃষ্টিকোণ থেকে দেখছে — একটি ভাষা কীভাবে প্রোগ্রামারকে রিপ্রেজেন্টেশনের খুঁটিনাটি লুকানোর সুযোগ দেয়।

২ · ইনফরমেশন হাইডিং — মূল অনুপ্রেরণামূলক নীতি

ইনফরমেশন হাইডিংInformation Hidingএকটি ADT-এর ইন্টারনাল রিপ্রেজেন্টেশন একটি সুনির্দিষ্ট ইন্টারফেসের পেছনে লুকিয়ে রাখার নীতি, যাতে ক্লায়েন্ট কোড ইমপ্লিমেন্টেশন-স্বাধীন থাকে। — M1/L04-এর reliability ডিজাইন গোলের সাথে সরাসরি সম্পর্কিত — একটি ADT-এর ইন্টারনাল রিপ্রেজেন্টেশন একটি সুনির্দিষ্ট ইন্টারফেসের (পাবলিক অপারেশন) পেছনে লুকিয়ে রাখলে, সেই ADT ব্যবহারকারী ক্লায়েন্ট কোড কীভাবে এটি ভেতরে ভেতরে বাস্তবায়িত তা থেকে সম্পূর্ণ স্বাধীন হয়ে যায় — ইমপ্লিমেন্টেশন পরে বদলানো যায় (যেমন একটি array-ভিত্তিক বাস্তবায়ন থেকে linked-list-ভিত্তিক বাস্তবায়নে সরে যাওয়া), যতক্ষণ ইন্টারফেসের observable আচরণ একই থাকে, ততক্ষণ কোনো ক্লায়েন্ট কোড ভাঙে না — এটি একটি সত্যিকারের, গুরুত্বপূর্ণ সফটওয়্যার- ইঞ্জিনিয়ারিং সুবিধা (কাপলিং কমায়)।

ক্লায়েন্ট কোড পাবলিক ইন্টারফেস — insert(), contains() লুকানো ইন্টারনাল রিপ্রেজেন্টেশন (sorted লিস্ট) কোনো পাবলিক মেথড এই স্তর সরাসরি এক্সপোজ করে না
ক্লায়েন্ট কোড শুধু পাবলিক ইন্টারফেসের মধ্য দিয়েই ADT-এর সাথে যোগাযোগ করে — ইন্টারনাল রিপ্রেজেন্টেশন সম্পূর্ণ লুকানো, তাই সেটি ভেঙে ফেলা (বা ইনভেরিয়েন্ট নষ্ট করা) সম্ভব নয়।

৩ · ইনভেরিয়েন্ট — এনক্যাপসুলেশন যা রক্ষা করা সম্ভব করে

ইনভেরিয়েন্ট · Invariant

একটি ইনভেরিয়েন্ট হলো এমন একটি প্রপার্টি, যা একটি ADT-এর ইন্টারনাল রিপ্রেজেন্টেশন সবসময় বজায় রাখতে গ্যারান্টি দেয়, এবং সেই ADT-এর প্রতিটি অপারেশন এই গ্যারান্টি বজায় রাখে — যেমন একটি "sorted list" ADT-এর ইনভেরিয়েন্ট হলো, এর ইন্টারনাল array/list প্রতিটি insert অপারেশনের পরেও সর্টেড থাকবে। এনক্যাপসুলেশন হলো ঠিক সেই জিনিস, যা এই ধরনের ইনভেরিয়েন্ট বজায় রাখা সম্ভবই করে — যদি ক্লায়েন্ট কোড সরাসরি ইন্টারনাল স্টেটে হাত দিতে পারত, তাহলে সহজেই ইনভেরিয়েন্ট ভেঙে ফেলা যেত (যেমন একটি sorted list-এর মাঝে সরাসরি একটি বড় মান insert করে দিলে)।

৪ · কোড: SortedListADT — ইনভেরিয়েন্ট প্রতিটি insert-এর পরে যাচাই

নিচের কোড সেলে SortedListADT ক্লাসের একটি লুকানো ইন্টারনাল লিস্ট (নেম-ম্যাংগলড __items, তাই ক্লাসের বাইরে থেকে সরাসরি অ্যাক্সেসযোগ্য নয়) এবং শুধুমাত্র দুটো পাবলিক অপারেশন আছে — insert(value) (সর্টেড অর্ডার বজায় রেখে) এবং contains(value) (একটি সরল এক্সিস্টেন্স চেক)। কোনো পাবলিক মেথড ইন্টারনাল রিপ্রেজেন্টেশনের সরাসরি মিউটেশন এক্সপোজ করে না। প্রতিটি ইনসার্টের পর ইন্টারনাল অবস্থা প্রিন্ট ও sorted কি না এক্সপ্লিসিটভাবে যাচাই করা হয়েছে, এবং সবশেষে ক্লাসের পাবলিক মেথড তালিকা দেখিয়ে নিশ্চিত করা হয়েছে যে কোনো মিউটেশন-এক্সপোজিং মেথড নেই।

Python
class SortedListADT:
    def __init__(self):
        self.__items = []   # নেম-ম্যাংগলড, লুকানো ইন্টারনাল রিপ্রেজেন্টেশন

    def insert(self, value):
        # সর্টেড পজিশন খুঁজে ঠিক জায়গায় বসানো হয় -- ইনভেরিয়েন্ট বজায় থাকে
        pos = 0
        while pos < len(self.__items) and self.__items[pos] < value:
            pos += 1
        self.__items.insert(pos, value)

    def contains(self, value):
        # শুধু বুলিয়ান রিটার্ন করে -- ইন্টারনাল লিস্ট এক্সপোজ করে না
        for item in self.__items:
            if item == value:
                return True
        return False

    def _debug_snapshot(self):
        # শুধুমাত্র এই পাঠের ডেমোর জন্য -- ইনভেরিয়েন্ট যাচাই করতে,
        # প্রোডাকশন ADT-এর পাবলিক ইন্টারফেসের অংশ নয় (আন্ডারস্কোর দিয়ে চিহ্নিত)
        return list(self.__items)


def is_sorted(seq):
    return all(seq[i] <= seq[i + 1] for i in range(len(seq) - 1))


adt = SortedListADT()
values_to_insert = [5, 1, 9, 3, 7, 2]   # ইচ্ছাকৃতভাবে অ-সর্টেড ক্রমে

for v in values_to_insert:
    adt.insert(v)
    snapshot = adt._debug_snapshot()
    print(f"insert({v}) -> ইন্টারনাল অবস্থা: {snapshot}  | sorted ইনভেরিয়েন্ট ধরে আছে: {is_sorted(snapshot)}")

print()
print("contains(7):", adt.contains(7))
print("contains(4):", adt.contains(4))

# পাবলিক ইন্টারফেসে সত্যিই কোনো মিউটেশন-এক্সপোজিং মেথড নেই তা নিশ্চিত করা
public_methods = [m for m in dir(adt) if not m.startswith("_")]
print()
print("পাবলিক মেথডসমূহ:", public_methods)

    
লক্ষ্য করুন — ইনপুট ক্রম [5, 1, 9, 3, 7, 2] সম্পূর্ণ অ-সর্টেড, তবুও প্রতিটি insert()-এর পরে প্রিন্ট হওয়া ইন্টারনাল অবস্থা সবসময় সর্টেড থাকে (sorted ইনভেরিয়েন্ট ধরে আছে: True — প্রতিবারই) — এটাই ইনভেরিয়েন্ট বাস্তবে কাজ করার প্রমাণ। আর শেষের public_methods তালিকায় লক্ষ্য করুন — শুধু contains ও insert আছে, __items বা _debug_snapshot কোনোটাই নেই (নাম আন্ডারস্কোর দিয়ে শুরু হওয়ায় বাদ পড়েছে) — কোনো পাবলিক মেথডই ইন্টারনাল লিস্ট সরাসরি এক্সপোজ বা মিউটেট করার সুযোগ দেয় না।
মূল কথা · Key takeaway

একটি ADT-এর আসল শক্তি তার আচরণে, ইমপ্লিমেন্টেশনে নয় — এবং সেই আচরণ (বিশেষত ইনভেরিয়েন্ট) নির্ভরযোগ্যভাবে বজায় রাখতে এনক্যাপসুলেশন/ইনফরমেশন হাইডিং অপরিহার্য। উপরের SortedListADT প্রমাণ করে — যদি ক্লায়েন্ট কোড ইন্টারনাল রিপ্রেজেন্টেশনে সরাসরি পৌঁছাতে না পারে, তাহলে সেই রিপ্রেজেন্টেশনের একটি গুরুত্বপূর্ণ প্রপার্টি (এখানে: sorted থাকা) প্রতিটি অপারেশনের পরেও নির্ভরযোগ্যভাবে ধরে রাখা যায়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ যদি SortedListADT-তে একটি পাবলিক get_raw_list() মেথড যোগ করে সরাসরি ইন্টারনাল লিস্ট রিটার্ন করা হতো, তাহলে কী সমস্যা হতে পারত?

Python-এ যদি সেই মেথড ইন্টারনাল লিস্ট অবজেক্টের একটি সরাসরি রেফারেন্স রিটার্ন করত (কপি নয়), তাহলে ক্লায়েন্ট কোড adt.get_raw_list().append(999)-এর মতো কিছু করে সরাসরি ইন্টারনাল লিস্টে একটি মান বসিয়ে দিতে পারত — সম্পূর্ণভাবে insert()-এর সর্টেড-পজিশন-খোঁজা লজিক বাইপাস করে। এতে sorted ইনভেরিয়েন্ট তাৎক্ষণিকভাবে ভেঙে যেত, এবং পরবর্তী contains() কল ভুল ফলাফল দিতে পারত (যদি contains() সর্টেড-নির্ভর কোনো অপ্টিমাইজেশন ব্যবহার করত) অথবা ADT-এর গ্যারান্টি একেবারেই অর্থহীন হয়ে যেত। এটাই দেখায় কেন "শুধু একটি getter থাকা"ও যথেষ্ট বিপজ্জনক — যদি সেটি মিউটেবল ইন্টারনাল স্টেটের সরাসরি রেফারেন্স ফাঁস করে দেয়।

প্র ০২ ADT ও OOP ক্লাসের সম্পর্ক ঠিক কী — একটি ক্লাস মানেই কি একটি ADT?

প্রতিটি ভালো-ডিজাইন করা ক্লাস একটি ADT বাস্তবায়ন করতে পারে, কিন্তু "ক্লাস" ও "ADT" সমার্থক নয়। ADT একটি ধারণাগত (conceptual) ধারণা — আচরণ ও গ্যারান্টির একটি সেট, ভাষা-নিরপেক্ষ। ক্লাস হলো OOP ভাষায় সেই ধারণা বাস্তবায়নের একটি মেকানিজম (M2/L07-এ যেমন বলা হয়েছে) — কিন্তু যদি একটি ক্লাসের সব ফিল্ড পাবলিক হয় (কোনো এনক্যাপসুলেশন ছাড়াই), তাহলে সেই ক্লাস প্রযুক্তিগতভাবে একটি ক্লাস, কিন্তু একটি ভালো ADT নয় — কারণ এটি কোনো ইনভেরিয়েন্ট গ্যারান্টি রক্ষা করে না। এমনকি non-OOP ভাষাতেও (যেমন C-এর মডিউল/হেডার ফাইল প্যাটার্ন, বা ML-এর মডিউল সিস্টেম) ADT বাস্তবায়ন করা সম্ভব — ক্লাস শুধু সবচেয়ে পরিচিত মেকানিজম, একমাত্র মেকানিজম নয়।

প্র ০৩ উপরের কোড সেলে insert() ফাংশনের ভেতরে ঠিক কোন লাইনটা sorted ইনভেরিয়েন্ট বজায় রাখার জন্য দায়ী, এবং কীভাবে?

মূল কাজটা করে while pos < len(self.__items) and self.__items[pos] < value: pos += 1 লাইনটি — এটি ইন্টারনাল লিস্টের শুরু থেকে খুঁজতে থাকে ঠিক কোন pos পজিশনে নতুন value-টি বসালে লিস্ট সর্টেড থাকবে (অর্থাৎ, প্রথম যে পজিশনে বিদ্যমান এলিমেন্ট নতুন মানের চেয়ে ছোট নয়)। তারপর self.__items.insert(pos, value) ঠিক সেই পজিশনেই মানটি বসায় — লিস্টের শেষে বা শুরুতে নয়, নির্বিচারে নয়। যেহেতু প্রতিটি insert কল এই একই লজিক অনুসরণ করে, এবং এটাই ইন্টারনাল লিস্ট পরিবর্তনের একমাত্র জায়গা (কারণ __items ক্লাসের বাইরে থেকে অ্যাক্সেসযোগ্য নয়), তাই লিস্টটি সবসময় সর্টেড থাকতে বাধ্য — এটাই এনক্যাপসুলেশন-নিশ্চিত ইনভেরিয়েন্টের প্রকৃত কার্যকারণ।

অনুশীলন

  1. চিন্তা করুন: SortedListADT-তে একটি তৃতীয় পাবলিক মেথড remove(value) যোগ করতে চাইলে, sorted ইনভেরিয়েন্ট বজায় রাখতে সেটি কীভাবে লিখতে হবে?

    যেহেতু একটি সর্টেড লিস্ট থেকে একটি এলিমেন্ট বাদ দিলে বাকি এলিমেন্টগুলোর আপেক্ষিক ক্রম অপরিবর্তিত থাকে, তাই sorted ইনভেরিয়েন্ট স্বয়ংক্রিয়ভাবেই বজায় থাকবে — যদি value খুঁজে পাওয়া যায় (contains-এর মতো লিনিয়ার স্ক্যান দিয়ে), শুধু সেই ইনডেক্সে self.__items.pop(index) কল করলেই যথেষ্ট। কোনো re-sorting দরকার নেই, কারণ একটি এলিমেন্ট সরিয়ে ফেললে বাকি সর্টেড ক্রম নষ্ট হয় না — নতুন কোনো ভুল-পজিশনড মান insert হচ্ছে না, শুধু একটি বিদ্যমান মান বাদ যাচ্ছে।

  2. পরীক্ষা করুন: উপরের কোড সেলে values_to_insert-এর তালিকায় একটি ডুপ্লিকেট মান যোগ করুন (যেমন [5, 1, 9, 3, 7, 2, 5]) এবং Run চেপে দেখুন ইনভেরিয়েন্ট এখনও ধরে থাকে কি না।

    হ্যাঁ, ইনভেরিয়েন্ট এখনও ধরে থাকবে — শেষ insert(5)-এর পরে ইন্টারনাল অবস্থা হবে [1, 2, 3, 5, 5, 7, 9], এবং is_sorted এখনও True রিটার্ন করবে, কারণ is_sorted-এর কন্ডিশন seq[i] <= seq[i+1] — কড়াভাবে < নয়, বরং <= — সমান মান পাশাপাশি থাকলেও সেটাকে বৈধ সর্টেড ক্রম হিসেবে গণ্য করে। insert()-এর while লুপের কন্ডিশনও একই কারণে < (কঠোর) ব্যবহার করে, যাতে ডুপ্লিকেট মান সঠিকভাবে বিদ্যমান একই মানের ঠিক পরে বসে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ পরবর্তী পাঠ — অ্যারে, রেকর্ড ও পয়েন্টার — L45-এর কম্পোজিট টাইপগুলোর কংক্রিট বাস্তবায়ন কভার করবে।
  • Data Structures & Algorithms কোর্স সঙ্গী কোর্স stack, queue, list-এর মতো ADT সেই কোর্সে অ্যালগরিদমিক-আচরণের দৃষ্টিকোণ থেকে পড়ানো হয়েছে — এই পাঠে দেখানো ভাষা-মেকানিজম (এনক্যাপসুলেশন) সেই ADT-গুলোরই বাস্তবায়নের ভিত্তি।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture ও Programming Languages & Compiler Design — সব এক জায়গায়।
আগের পাঠ
প্রিমিটিভ ও কম্পোজিট ডেটা টাইপ