اگر تا حالا به سبک تابعی، اعلامی یا امری برنامهنویسی کردهاید، ممکن است تغییر تمرکز به برنامهنویسی شیءگرا (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__()
«متد دونْدِر» ویژه (کوتاهشدهی «متد دو زیرخط») یعنی __init__() برای سفارشیسازی نمونههای کلاس به کار میرود و میتوان از آن برای مقداردهی اولیه به ویژگیها یا خواص نمونهی شیءها استفاده کرد.
__init__() بهخاطر نقشی که در مقداردهی اولیه به ویژگیهای نمونه دارد، class constructor یا initializer هم نامیده میشود.
__init__() یک پارامتر اجباری به اسم self میگیرد که به شیء تازه مقداردهیشده یا ساختهشده اشاره میکند.
بعد از آن میتوان دادهی ویژگیها یا خواص نمونه را بهعنوان آرگومانهای __init__()، پس از پارامتر self، پاس داد.
در ادامه، MyClass حالا ویژگیهای نمونهای به اسم location_x و location_y دارد.
همانطور که میبینید، این دو ویژگی به اندیسهای اول و دوم آرگومان location (یک تاپل) که به __init__() پاس داده شده، اختصاص یافتهاند.
ویژگیهای 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 در بالا فقط باید یک آرگومان پاس بدهید؛ Python هنگام فراخوانی کلاس خودش self را پاس میدهد.
method یک function است که یا به خود کلاس (که به آن متد کلاس میگویند و در تمرینی بعدی دربارهاش صحبت خواهیم کرد) یا به یک نمونه از کلاس (شیء) گره خورده است.
متدهایی که روی یک شیء (نمونه) عمل میکنند باید با 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 در بدنهی توابع و بهجای کد واقعی به کار رفته است.
کلیدواژهی 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
الن دوست دارد 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)، یک list از اشیاء Alien() بسازد.
برای مثال:
>>> 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)