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

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

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

Xcode লঞ্চ করে আমাদের সেই Hello World নামক কমান্ড-লাইন টাইপ অ্যাপটাই ওপেন করুন। File -> New -> File এ গিয়ে নিচের মত করে Objective-C class সিলেক্ট করে Next করুন, Screen Shot 2014-05-17 at 6.03.32 PM

এরপর ক্লাসের নাম হিসেবে দিন food এবং Subclass of ফিল্ডে লিখুন NSObject. নিচের ছবির মত করে। তারপর Next ক্লিক করুন। NSObject হচ্ছে অবজেক্টিভ সি এর একটি টপ লেভেল ক্লাস যাকে মোটা মুটি অন্য সবাই ইনহেরিট করে। Screen Shot 2014-05-17 at 6.04.08 PM

শেষে Group এবং Targets ঠিক মত সিলেক্ট/চেক করা আছে কিনা দেখে নিন এবং Create বাটনে ক্লিক করুন। Screen Shot 2014-05-17 at 6.04.26 PM

এখন আপনার প্রজেক্ট ন্যাভিগেটরে food.h এবং food.m নামের দুইটি নতুন ফাইল দেখতে পাবেন। এ দুটোই হচ্ছে আমাদের food ক্লাসের ইন্টারফেস এবং ইমপ্লেমেন্টেশন ফাইল। Screen Shot 2014-05-17 at 6.46.43 PM

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

// food.h

#import 

@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 
#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;
}

৪ নাম্বার লাইনে ইটারফেস import করার পর মেইন ফাংশনের মধ্যে থেকে food ক্লাসের অবজেক্ট ইন্সট্যন্সিয়েট করা হয়েছে alloc init প্যাটার্ন এর মধ্যমে। অজেক্টিভ-সি তে একটি অবজেক্ট ইন্সট্যন্সিয়েট করা দুই ধাপে সম্পন্ন হয়। প্রথমে অবজেক্টটির জন্য কিছু মেমোরি অ্যালোকেট করে নেয়া হয় alloc নামের বিল্টইন মেথডের মাধ্যমে তারপর সেটিকে ইনিশিয়ালাইজ করা হয়। আর অবজেক্টটিকে একটি পয়েন্টার হিসেবে স্টোর করা হয় কারন ইতোমধ্যে অনেকবারই বলা হয়েছে যে অবজেক্টিভ-সি তে সব রকম অবজেক্টকেই পয়েন্টার হিসেবে স্টোর করতে হয়। আর তাই food pizza = … না লিখে food *pizza = … লেখা হয়েছে। এখানে food হচ্ছে যে ক্লাসের অবজেক্ট বানাতে চান তার নাম এবং pizza হচ্ছে অবজেক্টের নাম। এর পরেই এই অবজেক্টের প্রোপার্টি সেট করা হয়েছে item ভ্যারিয়েবলের accessor method (গেটার/সেটার) ব্যবহার করে যেটা স্বয়ংক্রিয় ভাবেই হয়ে যায় Xcode 5 এ। এর আগে এই ভ্যারিয়েবলকে @synthesize করে এর জন্য (গেটার/সেটার) জেনারেট করতে হত। গেটার হিসেবে শুধুমাত্র ভ্যারিয়েবলটির নাম ব্যবহার করলেই হবে এবং সেটার হিসেবে ভ্যারিয়েবলটির প্রথম অক্ষরকে বড় হাতের করে এবং সামনে set বসিয়ে দিয়ে এক্সেস করলেই হবে। যা করা হয়েছে উপরের ১৪, ১৫ নাম্বার লাইনে। ১৪ নাম্বারে ভ্যালু সেট করা হয়েছে এবং ১৫ নাম্বারে ভ্যালু চেয়ে নেয়া হয়েছে। আবার, dot syntax ব্যবহার করেও এই অবজেক্টের প্রোপার্টি গুলোকে কিভাবে এক্সেস করা যেতে পারে তা দেখানো হয়েছে ১৮ এবং ১৯ নাম্বার লাইনে। এরপরে ২১ নাম্বার লাইনে [রিসিভার ম্যাসেজ] প্যাটার্ন মোতাবেক food অবজেক্টের grab মেথডকে কল করা হয়েছে। আর আলোচ্য অংশে যে, [ ] এর ব্যবহার সেটাকে অবজেক্টিভ-সি স্টাইল মনে করেই আগাতে পারেন। Command+R চেপে এই অ্যাপকে রান করালে এর আউটপুট নিচের মতই আসবে এবং সেটা অ্যানালাইসিস করলেই বুঝতে পারবেন আপনি অবজেক্ট অরিয়েন্টেশনে ঢুকে পরেছেন। Screen Shot 2014-05-17 at 7.54.44 PM

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

// food.h

#import 

@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 
#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;
}

কিন্তু এবার grab মেথড জানাচ্ছে যে, আপনি একটা পিৎজা খাচ্ছেন কিন্তু সাথে আপনার একটা কমন পছন্দের আইটেম আছে লেমোনেড। Screen Shot 2014-05-17 at 8.35.57 PM

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

// food.h

#import 

@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 
#import "food.h"

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

    @autoreleasepool {

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

        [choice grab];

    }
    return 0;
}

উপরের কোড ব্লক লক্ষ্য করলে দেখবেন যে, food ক্লাসের অবজেক্ট choice কে ইনিশিয়ালাইজ করা হয়েছে initWithItem নামক কাস্টম ইনিশিয়ালাইজার মেথড দিয়ে। আর এই initWithItem মেথড food ক্লাসের একটি প্রোপার্টি যার নাম item সেটার ভ্যালু সেট করে দেয়। আর তাই, অবজেক্ট ইনিশিয়ালিয়াজেশনের পর পরই যদি আমরা grab মেথড কল করি যেটা কিনা কনসোলে ওই আইটেমটা খাওয়ার কথাটাই প্রিন্ট করার কথা – তাহলে নিচের মত ঠিক ঠাক আউটপুট আসে। Screen Shot 2014-05-18 at 3.27.30 AM

SHARE