یک باشگاه ورزشی در کرمان عضویت جلسهای میفروشد و برنامه پذیرش باید بداند از هر عضویت چند جلسه مانده است. نوشتهشده بهشکل کلاس، شش سطر است:
class Ozviyat:def __init__(self, nam, noe, jalase):self.nam, self.noe, self._mande = nam, noe, jalasedef estefade(self):self._mande -= 1return self._mande
بعد ozv = Ozviyat("Roya", "jalasei", 12)، و ozv.estefade() عدد ۱۱ میدهد و بار بعد ۱۰. کلاس نقشه است و شی یک چیزِ ساختهشده از روی آن، پس عضویت دومی که برای بهنام ساخته شود شمارش خودش را دارد و از اولی هیچ خبری ندارد. __init__ وقتی یک شی ساخته میشود اجرا میشود، نه وقتی کلاس تعریف میشود، و برای همین است که کلاس تا چیزی از رویش ساخته نشود هیچ خرجی ندارد.
همان شمارنده، تمام دلیل وجود این کلاس است. بدون کلاس باید جایی بنشیند که کل برنامه باشگاه ببیندش، و هر تکه کدی که یک جلسه ثبت میکند باید یادش بماند کمش کند. با کلاس، جای دیگری برای گذاشتنش نیست و راهی برای کم کردن جلسه بدون رد شدن از estefade وجود ندارد.
self کلیدواژه نیست و متد چیز خاصی نیست
self همان نمونه است و پایتون خودش آن را به تابع میدهد. تمام ماجرا همین است، و اثباتش این است که میتوانید خودتان بدهیدش: Ozviyat.estefade(ozv) همان فراخوانی ozv.estefade() است و روی همان عضویت بالا عدد ۱۰ را میدهد. نقطه، یک جستوجوست که تابعی ساده را روی کلاس پیدا میکند و شیِ سمت چپ را به آن میدهد. هیچ چیز پنهانی در کار نیست، و هر زبانی که این را جور دیگری مینویسد دارد همین چیدمان را مینویسد.
چیزی که این چیدمان میخرد یک جلوی کوچک است و یک پشتِ بزرگ. برنامه پذیرش از یک عضویت سه نام را به کار میبرد و کلاس بیشتر از اینها نگه میدارد، و بقیهاش به هیچکس مربوط نیست. زیرخط ابتدای _mande هم پایتون است که با ادب خواهش میکند، نه چیزی که جلوی کسی را بگیرد. همین جدا کردن جلو از پشت، موضوع نقشه، و نمونههایی که از رویش ساخته میشوند است.
دو کلاس به سه شکل میتوانند به هم مربوط باشند، و یکی از سه بیش از حد استفاده میشود
باشگاه عضویت ماهانه هم اضافه میکند: نامحدود است، شمارنده ندارد، و با عضویت جلسهای یک سطر کد مشترک ندارد. با این حال باید همانجا پشت پیشخوان کار کند. سه چیدمان در دسترس است و جای هم نمینشینند.
ترکیب یک شی را داخل شی دیگر میگذارد و وراثت یک کلاس را زیر کلاس دیگر. عضویت یک قفسه رختکن دارد و صدایش میزند؛ هیچکس قفسه را به کدی که انتظار عضویت دارد نمیدهد، پس آن رابطه فقط نگهداری است. وراثت ادعای دیگری است: عضویت سالانه یک عضویت است، و هر چیزی که برای گرفتن عضویت نوشته شده باید بدون اینکه به آن بگویند قبولش کند.
سومی به هیچ رابطهای نیاز ندارد. هر چیزی که متد estefade داشته باشد کار میکند و هیچ چیز از قبل بررسی نمیشود، پس کلاس عضویت ماهانه و کلاس عضویت جلسهای میتوانند یک سطر مشترک نداشته باشند و هر دو به برنامه پذیرش داده شوند. هر دو به estefade() جواب میدهند و کل شرط همین است. نام گذاشتن روی این شرط بدون طلب کردن وراثت، نام گذاشتن روی یک توانایی، بدون نام بردن از پیادهسازی است، و تصمیم اینکه واقعاً کدامیک از این سه را دارید در به اشتراک گذاشتن رفتار، و جایی که تبدیل به تله میشود است.
ابزار، و سالی که این حرف درست است
بیشتر کلاسهایی که آدمها مینویسند داده نگه میدارند و رفتاری ندارند، و آنها اصلاً نباید با دست نوشته شوند. @dataclass خودش __init__ و __repr__ و __eq__ را مینویسد، در پایتون 3.7 در سال ۲۰۱۸ آمد، و جای تایپ کردن این سه یا رفتن سراغ یک کتابخانه بیرونی برای فرار از آنها را گرفت. یک Barname دوخطی بهشکل Barname(onvan='qodrat', jalase=12) چاپ میشود نه بهشکل یک نشانی حافظه، و دو نمونه dataclass با فیلدهای برابر، برابر شمرده میشوند، که در کلاس دستنویس تقریباً هیچوقت درست نیست و تقریباً همیشه همان چیزی است که میخواستید.
برای چیدمان سوم، حالا در خود زبان یک اسم هست. Protocol میگوید یک چیز چه کاری بلد است، بدون اینکه بگوید چطور، و هیچ کلاسی از آن ارث نمیبرد: اعلام کنید چیزی که پشت پیشخوان کار میکند estefade() دارد، و کلاس عضویت ماهانه فقط با داشتن آن متد شرط را برآورده میکند. در پایتون 3.8 در سال ۲۰۱۹ آمد و جای ارث بردن از یک کلاس پایه انتزاعی را گرفت که تنها کارش نوشتن همین قول بود. بررسیاش با mypy انجام میشود، متنباز و رایگان، و تنها چیز این صفحه است که نصب میخواهد؛ بقیه در کتابخانه استاندارد هستند.
تصمیم
دو پرسش تعیین میکند دو کلاس چطور باید به هم مربوط باشند
اینکه کد مشترک دارند یا نه، و اینکه یکی را میشود به هر چیزی داد که منتظر دیگری است یا نه. این دو پرسش یکی نیستند و فقط یکیشان درباره کمتر تایپ کردن است.
یکی میتواند جای دیگری بنشیند
- class OzviyatSalane(Ozviyat)
- class GhabelEstefade(Protocol)
نمیتواند جای دیگری بنشیند
- self.ghafase = Ghafase()
- به حال خودشان بگذارید
خانه وراثت همان است که اول سراغش میروند و تنها خانهای است که بیرون آمدن از آن سخت است. وراثتی که در هفته اول تصمیم گرفته شده، سال سوم هنوز دارد شکل کد را تعیین میکند.
اشتباه: مقدار مشترکی که روی خود کلاس نشسته است
باشگاه میخواهد تاریخ هر مراجعه ثبت شود. سطری که همه اول مینویسند این است:
class Ozviyat:tarikhha = []def sabt(self, t): self.tarikhha.append(t)
حالا عضویت رویا یک تاریخ ثبت میکند و عضویت بهنام یکی دیگر، و هر دو تاریخ در یک فهرست مینشینند. فهرستی که روی خود کلاس تعریف شود میان همه نمونهها مشترک است، چون فقط یک بار ساخته شده، همان وقتی که کلاس تعریف میشد. ozv_roya.tarikhha is ozv_behnam.tarikhha جواب True میدهد، و این تنها پرسشی است که تکلیف را روشن میکند. درستش این است که فهرست داخل __init__ ساخته شود، یعنی یک بار برای هر نمونه. تا وقتی باشگاه یک عضو دارد هیچچیز اشتباه به نظر نمیرسد.
جایی که این درس در ایران میایستد: چهار کلمه، بدون تصمیم
شیگرایی در ایران معمولاً بهشکل چهار کلمه وارد میشود که باید حفظ شوند: کپسولهسازی، وراثت، چندریختی و انتزاع. امتحان تعریفشان را میپرسد و نمره را همان تعریف میگیرد. اما سر کار هیچکس تعریف نمیپرسد؛ یک تصمیم پرسیده میشود و همیشه همان یکی است: این دو کلاس اصلاً باید به هم مربوط باشند یا نه، و اگر بله، به کدامیک از آن سه شکل. کسی که چهار کلمه را بیعیب بلد است و این تصمیم را یک بار هم نگرفته، در هفته اول کار گیر میکند و فکر میکند مسئله از نبلد بودن زبان است.
تمرینش کوتاه است و به امتحان ربطی ندارد. دو کلاس از کد خودتان بردارید و فقط دو پرسش بپرسید: کدی مشترک دارند، و آیا یکی را میشود جای دیگری به همان تابع داد. جواب این دو، هر بار یکی از سه چیدمان بالا یا هیچکدام را بیرون میدهد. چیزی که هنوز نگفتیم این است که وقتی جواب «وراثت» غلط از آب درمیآید، اسم آن غلط چیست و چه اصلی از قبل هشدارش را داده بود، که در پنج اصل، و اینکه هرکدام واقعاً جلوی چه چیزی را میگیرد است. همین سه چیدمان وقتی بهقدر کافی تکرار شوند اسم میگیرند و میشوند الگوهای طراحی؛ و اینکه اصلاً کلاس ظرف درستی هست یا نه، پرسش قدیمیتری است زیر سازههای اصلی برنامه، که خودش زیر ایدههایی که با عوض شدن زبان از بین نمیروند است.