ক্লাস ও অবজেক্ট

ভূমিকাঃ অন্যান্য অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং ল্যাঙ্গুয়েজের মতই অবজেক্টিভ-সি তেও - প্রোপার্টি, মেথড, ক্লাস, অবজেক্ট এই ব্যাপার গুলো বেশ ভালো ভাবেই আছে। আর এর আগে অনেক বার বলাই হয়েছে যে, সাধারণ সি ল্যাঙ্গুয়েজের সাথে অবজেক্ট ওরিয়েন্টেড ফিচার যুক্ত করেই অবজেক্টিভ-সি আত্মপ্রকাশ করেছে। এই অবজেক্ট ওরিয়েন্টেড ফিচারই মূলত সি থেকে অবজেক্টিভে-সি কে আলাদা করে। এখানেও একটি ক্লাসের মধ্যে কিছু রি-ইউজ্যাবল প্রোপার্টি-মেথড ডিফাইন করা এবং সেই ক্লাস থেকে একটি অবজেক্ট ইন্সট্যান্সিয়েট করে ওই প্রোপার্টি এবং মেথড গুলোর সাথে যোগাযোগ করার ব্যাপার গুলোও আছে। আর অবজেক্টিভে সি কে সি++ এর সাথে তুলনা করা যায় একটি জায়গায় সেটা হল, এটা একটা ক্লাসের ইন্টারফেসকে তার ইমপ্লেমেন্টেশন থেকে পৃথক করে। ইন্টারফেস ডিফাইন করে একটি ক্লাসের পাবলিক প্রোপার্টি ও মেথড গুলোকে আর ইমপ্লেমেন্টেশন ডিফাইন করে সেগুলোর মুল কার্যক্রম বা কার্যনীতি। আগের চ্যাপ্টারের ফাংশন এর ডিক্লেয়ারেশন + ইমপ্লেমেন্টেশন = ডেফিনেশন এর মতই।

ক্লাস তৈরিঃ এই চ্যাপ্টারে আমরা আদর্শ একটি ক্লাসের উদাহরণ হিসেবে food নামের একটি ক্লাস তৈরি করব যার ইন্টারফেস থাকবে food.h ফাইলে যেটাকে হেডারও বলা হয় আর ইমপ্লেমেন্টেশন থাকবে food.m ফাইলে। এ দুটোই Objective C ক্লাসের স্ট্যান্ডার্ড এক্সটেনশন। হেডার ফাইলের মাধ্যমে অন্য ক্লাস এই ক্লাসের সাথে যোগাযোগ করবে আর ইমপ্লেমেন্টেশন ফাইলটি মূলত কম্পাইলার এর কাছে প্রসেস হবে।

ইন্টারফেসঃ food.h ফাইলে ক্লিক করলেই ডান পাশের এডিটরে এই ফাইলের টেম্পলেট কোড দেখতে পাবেন। আমরা এখানে একটি প্রোপার্টি এবং একটি মেথড ডিক্লেয়ার করবো। এডিট করার পর এই ফাইলের চেহারা হবে নিচের মত,

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;

@end

@interface ডিরেক্টিভ ব্যবহার করে ইন্টারফেস তৈরি করা হয় এবং এর পরেই ক্লাসের নাম এবং তার সুপার ক্লাসের নাম একটি কোলন দিয়ে পৃথক করে লেখা হয়। @property ডিরেক্টিভ ব্যবহার করে পাবলিক প্রোপার্টি ডিক্লেয়ার করা হয়। copy অ্যাট্রিবিউট দিয়ে এটির মেমোরি ম্যানেজমেন্ট এর ধরন বোঝানো হচ্ছে। item এ অ্যাসাইন করা ভ্যালু সরাসরি পয়েন্টার টাইপ না হয়ে কপি টাইপ হবে। সামনের চ্যাপ্টারে এ ব্যাপারে আরও বিস্তারিত থাকবে। -(void)grab দিয়ে একটি grab নামের মেথড ডিক্লেয়ার করা হচ্ছে যার কোন প্যারামিটার নাই এবং এটা কোন কিছু রিটার্নও করে না। - সাইন দিয়ে এটা যে একটা instance method তা বোঝানো হয়। instance method এবং class method এর ব্যবহার সামনের চ্যাপ্টারে বিস্তারিত আসবে। বিঃ দ্রঃ অনেকেই এই ইন্টারফেস ফাইলেই কিছু প্রোটেক্টেড ভেরিয়েবল ডিফাইন করে থাকেন কিন্তু এটা ভালো প্র্যাকটিস নয়। এ ব্যাপারে আমাদের একদম প্রথম অর্থাৎ সিরিজ শুরুর আগের পোস্টেও লেখা ছিল। এখানে

objective-c তে একটি ক্লাসের ইন্টারফেস সাধারণত .h ফাইল-এ লেখা হয়। অন্যদিকে .m ফাইল এর টেম্পলেট কোডেও ইন্টারফেস দেখা যায় যেটিকে মূলত বলা হয় ক্লাস এক্সটেনশন। .h ফাইলের এর ইন্টারফেসে সেই সব প্রপার্টি, মেথড ডিক্লেয়ার করা হয় যেগুলো হয়তবা অন্য ফাইল থেকেও ব্যবহার করা হতে পারে এবং যদি এমন কিছু প্রপার্টি, মেথড প্রয়োজন হয় যেগুলো শুধুই একটি .m ফাইলে ব্যবহৃত হবে অথবা প্রোটেক্টেড ভেরিয়েবল হিসেবে ব্যবহার করা হবে সেগুলোকে ক্লাস এক্সটেনশন এর মধ্যেই ডিক্লেয়ার করা ভালো অর্থাত ওই .m ফাইলের ইন্টারফেসের মধ্যে।

ইমপ্লেমেন্টেশনঃ নিচের কোড দেখে আমরা তার নিচে এটার বিভিন্ন লাইনের বর্ণনা করবো,

// food.m

#import "food.h"

@implementation food {
    // private instance variables here
    double makingCost;
}

- (void)grab {
    NSLog(@"Eating %@ :)", self.item);
}

@end

@implementation ডিরেক্টিভ ব্যবহার করে ইমপ্লেমেন্টেশন তৈরি করা করা হয়। এখানে ইন্টারফেসের মত সুপার ক্লাসের নাম উল্লেখ করতে হয় না। এখানে একটি {} দিয়ে এর মধ্যে প্রয়োজনীয় প্রাইভেট ইন্সট্যান্স ভেরিয়েবল ডিক্লেয়ার করা হয়ে থাকে। এরপর grab ফাংশনের কার্যক্রম ডিফাইন করা হচ্ছে অর্থাৎ এই ফাংশন কোথাও থেকে কল হলে আসলে এর প্রেক্ষিতে কি ঘটবে। self হচ্ছে এখানে Java, C++ বা PHP এর this কি-ওয়ার্ড এর মত। এটা আসলে- এই মেথড যে ইন্সট্যান্স এর মাধ্যমে কল হয় তার রেফারেন্স। এই ক্লাসে যদি payBill নামের আরও একটি মেথড ডিফাইন করা থাকতো এবং সেটিকে এখানেই কল করার দরকার হলে [self payBill] লিখে কল করা যেত।

ইন্সট্যান্সিয়েট ও ব্যবহারঃ কোন ফাইলের যদি অন্য একটি ক্লাসের সাথে যোগাযোগের দরকার হয় তাহলে তাকে অবশ্যই ওই ক্লাসের হেডার/ইন্টারফেস ফাইল import করতে হবে। ওই ক্লাসের ইমপ্লেমেন্টেশন ফাইলকে সরাসরি এক্সেস করা উচিত হবে না। তা নাহলে এই যে, ইন্টারফেসের মাধ্যমে ইমপ্লেমেন্টেশন থেকে পাবলিক API আলাদা করে কাজ করার নিয়ম, এটাই ভেস্তে যাবে। তো, এখন আমরা আমাদের main.m ফাইল থেকে এই ক্লাসের সাথে কাজ করব। এর জন্য নিচের মত করে main.m ফাইলকে আপডেট করতে হবে,

// main.m

#import <Foundation/Foundation.h>
#import "food.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        food *pizza = [[food alloc] init];

        // accessing value using accessor methods aka getter/setter
        [pizza setItem:@"Italian Pizza"];
        NSLog(@"Ordered a %@", [pizza item]);

        // accessing value using dot syntax
        pizza.item = @"Mexican Pizza";
        NSLog(@"Changed the item to a %@", pizza.item);

        [pizza grab];

    }
    return 0;
}

ক্লাস মেথড ও ক্লাস ভেরিয়েবলঃ উপরের উদাহরণে ইন্সট্যান্স লেভেলের প্রোপার্টি ও মেথড নিয়ে কাজ করা হয়েছে। অর্থাৎ প্রথমে একটি ক্লাসের অবজেক্ট তৈরি করা হয়েছে এবং সেই অবজেক্টকে ধরেই ওই ক্লাসের প্রোপার্টি ও মেথড এর সাথে যোগাযোগ করা হয়েছে। অব্জেক্টিভ-সি তে ক্লাস লেভেল প্রোপার্টি ও মেথডও আছে। এর ডিক্লেয়ারেশনও ইন্সট্যান্স লেভেল মেথড এর মতই কিন্তু শুরুতে - সাইনের বদলে + সাইন দেয়া হয়। যেমন নিচে একটি ক্লাস লেভেল মেথড ডিক্লেয়ার করা হয়েছে, স্বভাবতই হেডার ফাইলে। উল্লেখ্য, এই নতুন মেথডটি কিন্তু একটি NSString টাইপের প্যারামিটার নেয় কিন্তু কিছু রিটার্ন করে না।

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;
+ (void)setDefaultItem:(NSString *)item;

@end

আর এই ধরনের মেথডের ইমপ্লেমেন্টেশনও একই রকম ভাবে করা হয়। নিচে দুইটি কাজ একসাথে করা হয়েছে প্রথমত একটি ক্লাস লেভেল মেথডের ইমপ্লেমেন্টেশন লেখা হয়েছে এবং দ্বিতীয়ত সেই মেথডের মাধ্যমে একটি স্ট্যাটিক ভ্যারিয়েবলের ভ্যালু সেট করা হয়েছে যেহেতু অব্জেক্টিভ-সি তে ক্লাস লেভেল ভ্যারিয়েবল বলে কিছু নাই। অর্থাৎ defaultItem কে আমরা অন্য কোথাও থেকে ক্লাস লেভেল ভ্যারিয়েবল হিসেবে এক্সেস করতে পারবো না যেমন ভাবে setDefaultItem মেথড কে ক্লাস লেভেল মেথড হিসেবে এক্সেস করতে পারবো।

// food.m

#import "food.h"

static NSString *defaultItem;


@implementation food {
    // private instance variables here
    double makingCost;
}

- (void)grab {
    NSLog(@"Eating %@ with an obvious %@:)", self.item, defaultItem);
}

+ (void)setDefaultItem:(NSString *)item {
    defaultItem = [item copy];
}

@end

এখন main.m ফাইল থেকে আমরা নিচের মত করে এই ক্লাস লেভেল মেথডটিকে এক্সেস করতে পারি (১১ নাম্বার লাইনে সরাসরি ক্লাসের নাম দিয়েই ওটার একটি মেথড setDefaultItem কে ধরা যাচ্ছে) যেটা কিনা এর কাছে পাঠানো একটি স্ট্রিংকে ওই ক্লাসের একটি স্ট্যাটিক ভ্যারিয়েবলের ভ্যালু হিসেবে সেট করে। তারপর আমরা আগের উদাহরণে যা করেছিলাম ঠিক সেই কাজ আবার করেছি।

// main.m

#import <Foundation/Foundation.h>
#import "food.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        [food setDefaultItem:@"Lemonade"];

        food *pizza = [[food alloc] init];

        // accessing value using accessor methods aka getter/setter
        [pizza setItem:@"Italian Pizza"];
        NSLog(@"Ordered a %@", [pizza item]);

        // accessing value using dot syntax
        pizza.item = @"Mexican Pizza";
        NSLog(@"Changed the item to a %@", pizza.item);

        [pizza grab];

    }
    return 0;
}

কন্সট্রাক্টরঃ অবজেক্টিভ-সি তে সরাসরি কোন কন্সট্রাক্টর মেথড নাই। তবে অন্যান্য ল্যাঙ্গুয়েজের বিল্টইন কন্সট্রাক্টর মেথড যা করে (ক্লাস থেকে অবজেক্ট ইন্সট্যান্সিয়েট করার সময়ই কিছু সাধারণ কাজ করে ফেলা) ঠিক তাই করা যাবে নিচের পদ্ধতি অনুযায়ী। অবজেক্টিভ-সি তে একটি অবজেক্ট অ্যালোকেটেড হবার পর পরই init মেথড কল করার মাধ্যমে সেটি ইনিশিলাইজ হয়। যেমন উপরের উদাহরণে food *pizza = [[food alloc] init] লাইনে। আবার ক্লাস লেভেল ইনিশিলাইজেশনও আছে সেটা পরে দেখা যাবে। আগে এই পদ্ধতিতে কন্সট্রাকশন এর কাজ করি। init হচ্ছে একটি ডিফল্ট ইনিশিলাইজেশন মেথড। কিন্তু আপনি নিজেও আপনার মত করে একটা ইনিশিলাইজেশন মেথড তৈরি করতে পারেন। এটা খুব একটা কঠিন কাজ না, শুধু নরমাল টাইপের একটা মেথডের নামের আগে init শব্দটা ব্যবহার করতে হবে। নিচে আমরা সেরকম একটি ডিক্লেয়ার করেছি food.h ফাইলে যার নাম initWithItem,

// food.h

#import <Foundation/Foundation.h>

@interface food : NSObject

@property (copy) NSString *item;

-(void)grab;
-(id)initWithItem:(NSString *)item;

@end

এখন এই কাস্টম ইনিশিলাইজার এর ইমপ্লেমেন্টেশন করতে হবে নিচের মত করে,

// food.m

#import "food.h"
#import "food.h"

@implementation food {

}

- (void)grab {
    NSLog(@"Eating %@ :)", self.item);
}

- (id)initWithItem:(NSString *)item {
    self = [super init];
    if (self) {
        _item = [item copy];
    }
    return self;
}

@end

এখানে super, প্যারেন্ট ক্লাসকে নির্দেশ করে এবং self হচ্ছে সেই ইন্সট্যান্সকে/অবজেক্টকে নির্দেশ করে যেটা এই মেথডটিকে কল করে। ইনিশিলাইজার মেথডকে সবসময় ওই অবজেক্টের কাছে নিজের রেফারেন্সটাই রিটার্ন করতে হয়। আর তাই, এই মেথডের মধ্যে অন্যান্য কাজ করার আগে চেক করে নিতে হয় self আছে কিনা। তারপর আমরা ডাইরেক্ট _item = ... ব্যবহার করে এই ইন্সট্যান্স ভ্যারিয়েবলের ভ্যালু সেট করে দিলাম এই মেথডের সাথে আসা প্যারামিটার এর কন্টেন্টকে কপি করে। অর্থাৎ যখনই initWithItem ব্যবহার করে এই ক্লাস এর অবজেক্ট ইন্সট্যান্সিয়েট করা হচ্ছে ঠিক তখনই এই ক্লাসের একটি প্রোপার্টির ভ্যালু আমরা আসাইন করে দিচ্ছি। ঠিক এটাই সহজ ভাবে যেকোনো কন্সট্রাক্টর মেথডের কাজ। এখন এটা টেস্ট করার জন্য main.m ফাইলটিকে নিচের মত করে পরিবর্তন করে নিতে পারেন,

// main.m

#import <Foundation/Foundation.h>
#import "food.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {

        food *choice = [[food alloc] initWithItem:@"Pan Pizza"];

        [choice grab];

    }
    return 0;
}

পরের চ্যাপ্টারঃ প্রোপার্টি ( @property ) নিয়ে বিস্তারিত আলোচনা এবং বিভিন্ন অ্যাট্রিবিউট সাথে এর সম্পর্ক নিয়ে কিছু উদাহরণ এবং আলোচনা থাকবে।

Originally Posted Here

Last updated