আপনি যদি ফাংশনাল, ডিক্লারেটিভ, বা ইম্পারেটিভ স্টাইলে প্রোগ্রামিং করে থাকেন, তাহলে মনোযোগ অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং (OOP)-এর দিকে সরিয়ে নেওয়া হয়তো একটু অপরিচিত লাগতে পারে।
OOP পদ্ধতিতে প্রোগ্রামারকে একটি সমস্যাকে এক বা একাধিক objects হিসেবে মডেল করার কথা ভাবতে বলা হয়, যারা একে অন্যের সাথে ইন্টারঅ্যাক্ট করে, স্টেট সংরক্ষণ করে এবং ডেটার উপর কাজ করে।
অবজেক্ট বাস্তব জগতের সত্তা (যেমন গাড়ি বা বিড়াল) উপস্থাপন করতে পারে, অথবা আরও বিমূর্ত ধারণা (যেমন ইন্টিজার, যানবাহন বা স্তন্যপায়ী প্রাণী)।
প্রতিটি অবজেক্ট কম্পিউটার মেমোরিতে একটি অনন্য ইনস্ট্যান্স হয়ে ওঠে এবং সামগ্রিক মডেলের কিছু অংশ উপস্থাপন করে।
Classes হলো নতুন অবজেক্ট টাইপের সংজ্ঞা, আর এগুলো থেকেই অবজেক্টের নতুন instances তৈরি হয়।
এগুলো প্রায়ই ডেটার সাথে কোড বা ফাংশন একসাথে রাখে, যারা ওই ডেটার উপর কাজ করে।
এই অর্থে, ক্লাস হলো ব্লুপ্রিন্ট বা নির্দেশনার সমষ্টি, যা থেকে একই ধরনের অনেক অবজেক্ট তৈরি করে ব্যবহার করা যায়।
একটি জটিল প্রোগ্রামে অনেক ক্লাস থাকতে পারে, যার প্রত্যেকে নানা স্বাদের অবজেক্ট তৈরি করে।
ক্লাস থেকে অবজেক্ট তৈরির প্রক্রিয়াকে instantiation (বা ক্লাসের একটি ইনস্ট্যান্স তৈরি করা) বলা হয়।
Python-এ একটি ক্লাস ডেফিনিশন সহজ:
class MyClass:
# Class body goes here
ক্লাসের বডিতে ক্লাস ফিল্ড (যা properties, attributes, data members, বা variables নামেও পরিচিত) যোগ করা যায়:
class MyClass:
number = 5
string = "Hello!"
ক্লাসটিকে কল করে MyClass-এর একটি ইনস্ট্যান্স (অবজেক্ট) তৈরি করা যায় এবং একটি নামের সাথে বাঁধা যায় (একটি ফাংশন কল করার মতোই):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Class attributes ক্লাস থেকে তৈরি সব অবজেক্ট (বা ইনস্ট্যান্স)-এর মধ্যে ভাগ করা থাকে, এবং dot notation দিয়ে অ্যাক্সেস করা যায়, অর্থাৎ অবজেক্টের নামের পরে এবং অ্যাট্রিবিউটের নামের আগে একটি . বসিয়ে:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
ক্লাস অ্যাট্রিবিউট ক্লাসের শরীরেই, অন্য যেকোনো মেথডের আগে সংজ্ঞায়িত করা হয়। এগুলো ক্লাসের মালিকানাধীন, যা তাদের ক্লাসের বিভিন্ন ইনস্ট্যান্সের মধ্যে ভাগ করে নেওয়ার সুযোগ দেয়। যেহেতু এই অ্যাট্রিবিউটগুলো ভাগ করা, তাই এদের মান ক্লাস থেকে সরাসরি অ্যাক্সেস ও পরিবর্তন করা যায়। ক্লাস অ্যাট্রিবিউটের মান পরিবর্তন করলে ক্লাস থেকে তৈরি সব অবজেক্টের মান পরিবর্তিত হয়:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
সব সময় সিঙ্ক্রোনাইজড ডেটা-সহ অনেক অবজেক্ট থাকা বিশেষ কাজের নয়।
ভাগ্যক্রমে, ক্লাস থেকে তৈরি অবজেক্টগুলোকে তাদের নিজস্ব instance attributes (বা ইনস্ট্যান্স প্রোপার্টি, ভ্যারিয়েবল, বা ফিল্ড) দিয়ে কাস্টমাইজ করা যায়।
নাম থেকেই বোঝা যায়, ইনস্ট্যান্স অ্যাট্রিবিউট প্রতিটি অবজেক্টের জন্য আলাদা এবং স্বাধীনভাবে পরিবর্তন করা যায়।
__init__() দিয়ে অবজেক্ট ইনস্ট্যান্টিয়েশন কাস্টমাইজ করাবিশেষ "ডান্ডার মেথড" ("double underscore method"-এর সংক্ষিপ্ত রূপ) __init__() ক্লাস ইনস্ট্যান্স কাস্টমাইজ করতে ব্যবহৃত হয়, এবং এটি দিয়ে অবজেক্টের ইনস্ট্যান্স অ্যাট্রিবিউট বা প্রোপার্টি ইনিশিয়ালাইজ করা যায়।
ইনস্ট্যান্স অ্যাট্রিবিউট ইনিশিয়ালাইজ করার ভূমিকার কারণে __init__()-কে class constructor বা initializer-ও বলা হয়।
__init__() একটি আবশ্যক প্যারামিটার নেয় যার নাম self, যা সদ্য ইনিশিয়ালাইজ করা বা তৈরি করা অবজেক্টকে বোঝায়।
এরপর ইনস্ট্যান্স অ্যাট্রিবিউট বা প্রোপার্টির ডেটা self প্যারামিটারের পরে __init__()-এর আর্গুমেন্ট হিসেবে পাঠানো যায়।
নিচে, MyClass-এ এখন location_x ও location_y নামের ইনস্ট্যান্স অ্যাট্রিবিউট আছে।
আপনি দেখতেই পাচ্ছেন, দুটি অ্যাট্রিবিউট __init__()-এ পাঠানো location (একটি টাপল) আর্গুমেন্টের প্রথম ও দ্বিতীয় ইনডেক্সে অ্যাসাইন করা হয়েছে।
আপনি যখন ক্লাস ইনস্ট্যান্টিয়েট করেন এবং একটি অবজেক্ট তৈরি হয়, তখন ক্লাস ইনস্ট্যান্সের location_x ও location_y অ্যাট্রিবিউট ইনিশিয়ালাইজ হয়ে যাবে:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
মনে রাখবেন, উপরে MyClass ইনিশিয়ালাইজ করার সময় আপনাকে কেবল একটি আর্গুমেন্ট পাঠাতে হয়। ক্লাস কল করা হলে self পাঠানোর কাজ Python নিজেই সামলে নেয়।
একটি method হলো এমন একটি function যা হয় ক্লাসের সাথেই বাঁধা (যাকে class method বলা হয়, যা পরের একটি অনুশীলনীতে আলোচনা করা হবে), নাহয় ক্লাসের একটি ইনস্ট্যান্স (অবজেক্ট)-এর সাথে বাঁধা।
যে মেথডগুলো একটি অবজেক্ট (ইনস্ট্যান্স)-এর উপর কাজ করে, সেগুলোকে প্রথম প্যারামিটার হিসেবে self দিয়ে ডিফাইন করতে হয়।
এরপর বাকি প্যারামিটারগুলো আপনি একটি "সাধারণ" বা আনবাউন্ড ফাংশনের মতোই ডিফাইন করতে পারেন:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
অ্যাট্রিবিউট অ্যাক্সেসের মতোই, একটি মেথড কল করতে কেবল অবজেক্টের নামের পরে এবং মেথডের নামের আগে একটি . বসাতে হয়।
কল করা মেথডের প্রথম প্যারামিটার হিসেবে অবজেক্টের একটি কপি লাগে না, Python স্বয়ংক্রিয়ভাবে self বসিয়ে দেয়:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
ক্লাসের বাইরে যেভাবে অ্যাক্সেস করা হয়, ঠিক সেভাবেই ইনস্ট্যান্স মেথডের ভেতর থেকেও ক্লাস অ্যাট্রিবিউট অ্যাক্সেস করা যায়:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
আগের কনসেপ্ট অনুশীলনী ও প্র্যাকটিস অনুশীলনীর স্টাবে আপনি ফাংশনের বডিতে প্রকৃত কোডের বদলে pass কিওয়ার্ড ব্যবহৃত হতে দেখেছেন।
pass কিওয়ার্ডটি সিনট্যাক্টিক্যালি বৈধ একটি প্লেসহোল্ডার, এটি খালি ফাংশন বা ক্লাস ডেফিনিশনের জন্য Python-কে সিনট্যাক্স এরর ছুঁড়তে বাধা দেয়।
মূলত এটি Python ইন্টারপ্রেটারকে বলার একটি উপায়, 'চিন্তা করো না! আমি একসময় এখানে কোড বসাব, এখনো বসাইনি শুধু।'
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
এলেন একটি গেম বানাচ্ছেন, যেখানে খেলোয়াড়কে এলিয়েনদের সাথে লড়াই করতে হবে।
তিনি সবেমাত্র অবজেক্ট ওরিয়েন্টেড প্রোগ্রামিং (OOP) সম্পর্কে শিখেছেন এবং classes ব্যবহার করে তাঁর প্রোগ্রামে কী কী সুবিধা পাওয়া যায়, তা কাজে লাগাতে আগ্রহী।
এলেনের খুশিতে, আপনি সাহায্যের হাত বাড়িয়ে দিয়েছেন এবং তিনি আপনাকে সেই এলিয়েনদের প্রোগ্রাম করার দায়িত্ব দিয়েছেন, যাদের সাথে খেলোয়াড়কে লড়াই করতে হবে।
Alien ক্লাসটি এমন একটি কনস্ট্রাক্টর দিয়ে ডিফাইন করুন, যা দুটি প্যারামিটার <x_coordinate> এবং <y_coordinate> নেয় এবং সেগুলোকে x_coordinate ও y_coordinate ইনস্ট্যান্স ভ্যারিয়েবলে রাখে।
প্রতিটি এলিয়েনও শুরুতে ৩ হেলথ লেভেল নিয়ে যাত্রা করবে, তাই health ভ্যারিয়েবলটিও ইনিশিয়ালাইজ করতে হবে।
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
এখন প্রতিটি এলিয়েনের নিজের পজিশন ও হেলথ নিজের ভেতরেই ট্র্যাক করার ক্ষমতা থাকা উচিত।
hit মেথডএলেন চান Alien class-এ একটি hit মেথড থাকুক, যা কল করা হলে এলিয়েন অবজেক্টের হেলথ ১ কমিয়ে দেবে।
এতে তিনি নিজ হাতে এলিয়েনের হেলথ পরিবর্তন না করে সহজেই <alien>.hit() কল করতে পারবেন।
hit() হেলথ পয়েন্ট শূন্য পর্যন্ত কমাবে, নাকি শূন্যের নিচেও নামতে দেবে, সেটি আপনার সিদ্ধান্ত।
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive মেথডআপনি খেয়াল করলেন, হেলথ যদি কমতেই থাকে, একসময় সেটি সম্ভবত ০-এ গিয়ে ঠেকবে (বা আরও কমে যাবে!)।
এলেন যেন দ্রুত কল করে দেখতে পারেন এলিয়েনটি... মানে... জীবিত কি না, সেজন্য একটি is_alive মেথড যোগ করা ভালো হবে। 😉
<alien>.is_alive() একটি বুলিয়ান রিটার্ন করা উচিত।
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport মেথডএলেনের গেমে এলিয়েনদের টেলিপোর্ট করার ক্ষমতা আছে!
আপনাকে একটি teleport মেথড লিখতে হবে, যা নতুন x_coordinate ও y_coordinate মান নেয় এবং সেই অনুযায়ী এলিয়েনের কোঅর্ডিনেট পরিবর্তন করে।
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection মেথডস্বাভাবিকভাবেই, এলিয়েনদের যদি কোনো কিছুতে আঘাত লাগতে পারে, তাহলে এমন সংঘর্ষ কখন ঘটেছে তা শনাক্ত করার ক্ষমতাও তাদের থাকা দরকার।
তবে কোলিশন ডিটেকশন অ্যালগরিদম জটিল হতে পারে, আর সেটি কীভাবে বাস্তবায়ন করতে হয় তা আপনি এখনো জানেন না।
এলেন বলেছেন, তিনি এটি পরে করবেন, তবে ফাংশনালিটিটা পরে পুরোপুরি গড়ে তোলার কথা মনে করিয়ে দিতে তিনি চান collision_detection মেথডটি ক্লাসে থাকুক।
এটিকে কোনো এক ধরনের ভ্যারিয়েবল (সম্ভবত আরেকটি অবজেক্ট) নিতে হবে, কিন্তু আপনি আসলে এটুকুই জানেন।
মেথড ডেফিনিশনটি ক্লাসে রেখে কল করলে কোনো এরর হয় কি না, তা আপনি নিশ্চিত করতে হবে:
>>> alien.collision_detection(other_object)
>>>
এলেন নতুন একটি অনুরোধ নিয়ে আপনার কাছে ফিরে এসেছেন। গেমটি যতদিন চলবে, ততদিনে মোট কতজন এলিয়েন তৈরি হয়েছে, তিনি তার হিসাব রাখতে চান। তিনি বললেন, এটির সাথে স্কোরিং সিস্টেমের কিছু সম্পর্ক আছে।
যেমন:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
এখন পর্যন্ত আপনি যা করেছেন, এলেন তা খুব পছন্দ করেছেন, তবে তাঁর আরও একটি অনুরোধ আছে।
তিনি চান একটি স্বতন্ত্র ফাংশন (_ Alien() ক্লাসের বাইরে_), যা পজিশনের একটি অ্যারে (tuples আকারে) দিলে Alien() অবজেক্টের একটি list তৈরি করে।
যেমন:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Exercism-এ সাইন আপ করুন, Python ট্র্যাকের 17টি কনসেপ্ট146টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।