অবজেক্ট-ওরিয়েন্টেড ভাষাগুলির বৈশিষ্ট্য (Characteristics of Object-Oriented Languages)

প্রোগ্রামিং কমিউনিটিতে কোনো ভাষা অবজেক্ট-ওরিয়েন্টেড হওয়ার জন্য কী কী বৈশিষ্ট্য থাকা আবশ্যক, সে সম্পর্কে কোনো সর্বসম্মত মতামত নেই। Rust অনেক প্রোগ্রামিং প্যারাডাইম দ্বারা প্রভাবিত, যার মধ্যে OOP অন্তর্ভুক্ত; উদাহরণস্বরূপ, আমরা Chapter 13-এ ফাংশনাল প্রোগ্রামিং থেকে আসা বৈশিষ্ট্যগুলি অন্বেষণ করেছি। তর্কসাপেক্ষে, OOP ভাষাগুলি কিছু সাধারণ বৈশিষ্ট্য শেয়ার করে, যেমন অবজেক্ট, এনক্যাপসুলেশন এবং ইনহেরিটেন্স। আসুন দেখি সেই বৈশিষ্ট্যগুলির প্রত্যেকটির অর্থ কী এবং Rust সেগুলিকে সমর্থন করে কিনা।

অবজেক্ট ডেটা এবং আচরণ ধারণ করে (Objects Contain Data and Behavior)

এরিক গামা, রিচার্ড হেলম, রালফ জনসন এবং জন ভ্লিসাইডস-এর লেখা Design Patterns: Elements of Reusable Object-Oriented Software বইটি (অ্যাডিসন-ওয়েসলি প্রফেশনাল, ১৯৯৪), যাকে সাধারণত The Gang of Four বই বলা হয়, অবজেক্ট-ওরিয়েন্টেড ডিজাইন প্যাটার্নের একটি ক্যাটালগ। এটি OOP-কে এইভাবে সংজ্ঞায়িত করে:

অবজেক্ট-ওরিয়েন্টেড প্রোগ্রামগুলি অবজেক্ট দিয়ে গঠিত। একটি অবজেক্ট ডেটা এবং সেই ডেটার উপর কাজ করে এমন পদ্ধতি উভয়কেই প্যাকেজ করে। পদ্ধতিগুলিকে সাধারণত মেথড বা অপারেশন বলা হয়।

এই সংজ্ঞা ব্যবহার করে, Rust অবজেক্ট-ওরিয়েন্টেড: স্ট্রাক্ট এবং এনাম-এর ডেটা রয়েছে এবং impl ব্লকগুলি স্ট্রাক্ট এবং এনাম-এ মেথড সরবরাহ করে। যদিও মেথড সহ স্ট্রাক্ট এবং এনামগুলিকে অবজেক্ট বলা হয় না, তারা Gang of Four-এর অবজেক্টের সংজ্ঞা অনুযায়ী একই কার্যকারিতা প্রদান করে।

এনক্যাপসুলেশন যা ইমপ্লিমেন্টেশনের বিবরণ লুকায় (Encapsulation that Hides Implementation Details)

OOP-এর সাথে সাধারণত যুক্ত আরেকটি দিক হল এনক্যাপসুলেশন-এর ধারণা, যার অর্থ হল একটি অবজেক্টের ইমপ্লিমেন্টেশনের বিবরণ সেই অবজেক্ট ব্যবহার করা কোডের কাছে অ্যাক্সেসযোগ্য নয়। অতএব, একটি অবজেক্টের সাথে ইন্টারঅ্যাক্ট করার একমাত্র উপায় হল এর পাবলিক API-এর মাধ্যমে; অবজেক্ট ব্যবহার করা কোড অবজেক্টের অভ্যন্তরে প্রবেশ করে ডেটা বা আচরণ সরাসরি পরিবর্তন করতে সক্ষম হওয়া উচিত নয়। এটি প্রোগ্রামারকে অবজেক্ট ব্যবহার করা কোড পরিবর্তন না করেই একটি অবজেক্টের অভ্যন্তরীণ পরিবর্তন এবং রিফ্যাক্টর করতে সক্ষম করে।

আমরা Chapter 7-এ এনক্যাপসুলেশন কীভাবে নিয়ন্ত্রণ করতে হয় তা নিয়ে আলোচনা করেছি: আমরা pub কীওয়ার্ড ব্যবহার করে সিদ্ধান্ত নিতে পারি যে আমাদের কোডের কোন মডিউল, টাইপ, ফাংশন এবং মেথডগুলি পাবলিক হওয়া উচিত এবং ডিফল্টরূপে অন্য সবকিছু প্রাইভেট। উদাহরণস্বরূপ, আমরা একটি AveragedCollection স্ট্রাক্ট সংজ্ঞায়িত করতে পারি যাতে i32 মানের একটি ভেক্টর রয়েছে এমন একটি ফিল্ড থাকে। স্ট্রাক্টে একটি ফিল্ডও থাকতে পারে যাতে ভেক্টরের মানগুলির গড় থাকে, অর্থাৎ যখনই কারও এটির প্রয়োজন হয় তখনই গড় গণনা করার প্রয়োজন হয় না। অন্য কথায়, AveragedCollection আমাদের জন্য গণনা করা গড় ক্যাশে করবে। Listing 18-1-এ AveragedCollection স্ট্রাক্টের সংজ্ঞা রয়েছে:

pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}

স্ট্রাক্টটিকে pub হিসাবে চিহ্নিত করা হয়েছে যাতে অন্য কোড এটি ব্যবহার করতে পারে, কিন্তু স্ট্রাক্টের ভেতরের ফিল্ডগুলি প্রাইভেট থাকে। এটি এই ক্ষেত্রে গুরুত্বপূর্ণ কারণ আমরা নিশ্চিত করতে চাই যে যখনই তালিকাটিতে কোনও মান যোগ করা বা সরানো হয়, তখনও গড় আপডেট করা হয়। আমরা Listing 18-2-এ দেখানো স্ট্রাক্টে add, remove এবং average মেথড ইমপ্লিমেন্ট করে এটি করি:

pub struct AveragedCollection {
    list: Vec<i32>,
    average: f64,
}

impl AveragedCollection {
    pub fn add(&mut self, value: i32) {
        self.list.push(value);
        self.update_average();
    }

    pub fn remove(&mut self) -> Option<i32> {
        let result = self.list.pop();
        match result {
            Some(value) => {
                self.update_average();
                Some(value)
            }
            None => None,
        }
    }

    pub fn average(&self) -> f64 {
        self.average
    }

    fn update_average(&mut self) {
        let total: i32 = self.list.iter().sum();
        self.average = total as f64 / self.list.len() as f64;
    }
}

পাবলিক মেথড add, remove এবং average হল AveragedCollection-এর একটি ইনস্ট্যান্সের ডেটা অ্যাক্সেস বা পরিবর্তন করার একমাত্র উপায়। যখন add মেথড ব্যবহার করে list-এ কোনও আইটেম যোগ করা হয় বা remove মেথড ব্যবহার করে সরানো হয়, তখন প্রত্যেকটির ইমপ্লিমেন্টেশন প্রাইভেট update_average মেথডকে কল করে যা average ফিল্ড আপডেট করার কাজটিও পরিচালনা করে।

আমরা list এবং average ফিল্ডগুলিকে প্রাইভেট রেখেছি যাতে বাইরের কোডের জন্য সরাসরি list ফিল্ডে আইটেম যোগ বা সরানোর কোনও উপায় না থাকে; অন্যথায়, list পরিবর্তন হলে average ফিল্ডটি সিঙ্কের বাইরে চলে যেতে পারে। average মেথডটি average ফিল্ডের মান রিটার্ন করে, বাইরের কোডকে average পড়তে দেয় কিন্তু পরিবর্তন করতে দেয় না।

যেহেতু আমরা AveragedCollection স্ট্রাক্টের ইমপ্লিমেন্টেশনের বিবরণ এনক্যাপসুলেট করেছি, তাই আমরা ভবিষ্যতে ডেটা স্ট্রাকচারের মতো দিকগুলি সহজেই পরিবর্তন করতে পারি। উদাহরণস্বরূপ, আমরা list ফিল্ডের জন্য Vec<i32>-এর পরিবর্তে একটি HashSet<i32> ব্যবহার করতে পারি। যতক্ষণ add, remove এবং average পাবলিক মেথডগুলির স্বাক্ষর একই থাকে, ততক্ষণ AveragedCollection ব্যবহার করা কোড কম্পাইল করার জন্য পরিবর্তন করার প্রয়োজন হবে না। পরিবর্তে যদি আমরা list-কে পাবলিক করতাম, তাহলে এটি নাও হতে পারত: HashSet<i32> এবং Vec<i32>-এর আইটেম যোগ এবং সরানোর জন্য ভিন্ন মেথড রয়েছে, তাই বাইরের কোডটি যদি সরাসরি list পরিবর্তন করত তাহলে সম্ভবত পরিবর্তন করতে হত।

যদি কোনও ভাষাকে অবজেক্ট-ওরিয়েন্টেড হিসাবে বিবেচনা করার জন্য এনক্যাপসুলেশন একটি প্রয়োজনীয় দিক হয়, তাহলে Rust সেই প্রয়োজনীয়তা পূরণ করে। কোডের বিভিন্ন অংশের জন্য pub ব্যবহার করার বা না করার বিকল্পটি ইমপ্লিমেন্টেশনের বিবরণের এনক্যাপসুলেশন সক্ষম করে।

টাইপ সিস্টেম এবং কোড শেয়ারিং হিসাবে ইনহেরিটেন্স (Inheritance as a Type System and as Code Sharing)

ইনহেরিটেন্স হল এমন একটি মেকানিজম যার মাধ্যমে একটি অবজেক্ট অন্য অবজেক্টের সংজ্ঞা থেকে উপাদানগুলি ইনহেরিট করতে পারে, এইভাবে আপনাকে আবার সেগুলি সংজ্ঞায়িত না করেই প্যারেন্ট অবজেক্টের ডেটা এবং আচরণ লাভ করতে পারে।

যদি কোনও ভাষা অবজেক্ট-ওরিয়েন্টেড হওয়ার জন্য ইনহেরিটেন্স থাকা আবশ্যক হয়, তাহলে Rust তা নয়। কোনও ম্যাক্রো ব্যবহার না করে এমন একটি স্ট্রাক্ট সংজ্ঞায়িত করার কোনও উপায় নেই যা প্যারেন্ট স্ট্রাক্টের ফিল্ড এবং মেথড ইমপ্লিমেন্টেশন ইনহেরিট করে।

যাইহোক, আপনি যদি আপনার প্রোগ্রামিং টুলবক্সে ইনহেরিটেন্স রাখতে অভ্যস্ত হন, তাহলে আপনি Rust-এ অন্যান্য সমাধান ব্যবহার করতে পারেন, প্রাথমিকভাবে ইনহেরিটেন্সের জন্য আপনার কারণের উপর নির্ভর করে।

আপনি দুটি প্রধান কারণে ইনহেরিটেন্স বেছে নেবেন। একটি হল কোড পুনঃব্যবহারের জন্য: আপনি একটি টাইপের জন্য নির্দিষ্ট আচরণ ইমপ্লিমেন্ট করতে পারেন এবং ইনহেরিটেন্স আপনাকে সেই ইমপ্লিমেন্টেশনটি অন্য টাইপের জন্য পুনরায় ব্যবহার করতে সক্ষম করে। আপনি Rust কোডে ডিফল্ট trait মেথড ইমপ্লিমেন্টেশন ব্যবহার করে সীমিত উপায়ে এটি করতে পারেন, যেটি আপনি Listing 10-14-এ দেখেছিলেন যখন আমরা Summary trait-এ summarize মেথডের একটি ডিফল্ট ইমপ্লিমেন্টেশন যোগ করেছি। Summary trait ইমপ্লিমেন্ট করা যেকোনো টাইপের জন্য কোনও অতিরিক্ত কোড ছাড়াই summarize মেথড উপলব্ধ থাকবে। এটি একটি প্যারেন্ট ক্লাসের একটি মেথডের ইমপ্লিমেন্টেশন থাকার এবং একটি ইনহেরিটিং চাইল্ড ক্লাসেরও মেথডের ইমপ্লিমেন্টেশন থাকার অনুরূপ। আমরা যখন Summary trait ইমপ্লিমেন্ট করি তখনও আমরা summarize মেথডের ডিফল্ট ইমপ্লিমেন্টেশন ওভাররাইড করতে পারি, যা একটি চাইল্ড ক্লাস প্যারেন্ট ক্লাস থেকে ইনহেরিট করা একটি মেথডের ইমপ্লিমেন্টেশন ওভাররাইড করার অনুরূপ।

ইনহেরিটেন্স ব্যবহার করার অন্য কারণটি টাইপ সিস্টেমের সাথে সম্পর্কিত: একটি চাইল্ড টাইপকে প্যারেন্ট টাইপের মতো একই জায়গায় ব্যবহার করতে সক্ষম করা। এটিকে _পলিমরফিজম_ও বলা হয়, যার অর্থ হল আপনি যদি কিছু বৈশিষ্ট্য শেয়ার করেন তবে রানটাইমে একাধিক অবজেক্ট একে অপরের জন্য প্রতিস্থাপন করতে পারেন।

পলিমরফিজম (Polymorphism)

অনেকের কাছে, পলিমরফিজম ইনহেরিটেন্সের সমার্থক। কিন্তু এটি আসলে একটি আরও সাধারণ ধারণা যা একাধিক টাইপের ডেটা নিয়ে কাজ করতে পারে এমন কোডকে বোঝায়। ইনহেরিটেন্সের জন্য, সেই টাইপগুলি সাধারণত সাবক্লাস।

Rust পরিবর্তে বিভিন্ন সম্ভাব্য টাইপের উপর অ্যাবস্ট্রাক্ট করতে জেনেরিক ব্যবহার করে এবং সেই টাইপগুলিকে কী সরবরাহ করতে হবে তার উপর সীমাবদ্ধতা আরোপ করতে trait bound ব্যবহার করে। এটিকে কখনও কখনও বাউন্ডেড প্যারামেট্রিক পলিমরফিজম বলা হয়।

ইনহেরিটেন্স সম্প্রতি অনেক প্রোগ্রামিং ভাষায় একটি প্রোগ্রামিং ডিজাইন সমাধান হিসাবে অনুগ্রহ হারিয়েছে কারণ এটি প্রায়শই প্রয়োজনের চেয়ে বেশি কোড শেয়ার করার ঝুঁকিতে থাকে। সাবক্লাসগুলির সর্বদা তাদের প্যারেন্ট ক্লাসের সমস্ত বৈশিষ্ট্য শেয়ার করা উচিত নয় তবে ইনহেরিটেন্সের সাথে তা করবে। এটি একটি প্রোগ্রামের ডিজাইনকে কম নমনীয় করে তুলতে পারে। এটি সাবক্লাসগুলিতে এমন মেথড কল করার সম্ভাবনাও তৈরি করে যা অর্থবোধক নয় বা ত্রুটির কারণ হয় কারণ মেথডগুলি সাবক্লাসে প্রযোজ্য নয়। এছাড়াও, কিছু ভাষা শুধুমাত্র একক ইনহেরিটেন্স-এর অনুমতি দেবে (অর্থাৎ একটি সাবক্লাস শুধুমাত্র একটি ক্লাস থেকে ইনহেরিট করতে পারে), যা একটি প্রোগ্রামের ডিজাইনের নমনীয়তাকে আরও সীমাবদ্ধ করে।

এই কারণগুলির জন্য, Rust ইনহেরিটেন্সের পরিবর্তে trait অবজেক্ট ব্যবহার করার ভিন্ন পদ্ধতি গ্রহণ করে। আসুন দেখি কিভাবে trait অবজেক্ট Rust-এ পলিমরফিজম সক্ষম করে।