چهار الگوی ساختاری نامدار همه یک سطر مشترک دارند: self.darun = darun. یک شی، شی دیگری را نگه میدارد و بیشتر فراخوانیها را به آن پاس میدهد. تفاوتشان در این است که پوسته بیرونی دقیقا چه چیزی را عوض میکند.
- مبدل نامها را عوض میکند. کتابخانهای که
send()دارد و کد شماersal()میخواهد. - تزیینگر رفتار را عوض میکند. همان امضا، با کاری که دورش اضافه شده، مثل ثبت زمان یا کش کردن جواب.
- نما تعداد را عوض میکند. یک فراخوانی بهجای شش فراخوانی که همیشه با هم میآیند.
- وکیل دسترسی را عوض میکند. همان امضا، با یک بررسی یا یک تاخیر پیش از پاس دادن.
هیچکدام از این چهار تا شی داخلی را عوض نمیکنند و هیچکدام از آن ارث نمیبرند. برای همین روی کلاسی که خودتان ننوشتهاید هم کار میکنند، که تنها دلیل وجودشان است: کتابخانهای که نمیتوانید تغییرش دهید.
در پایتون، سه تای این چهار تا معمولا تابعاند
تزیینگر در پایتون کلمه خودش را دارد و لازم نیست کلاس بسازید. @functools.cache بالای یک تابع دقیقا همان الگوست: امضای تابع دستنخورده میماند و یک لایه کش دورش نشسته. نوشتن نسخه خودتان هم چند سطر است و همان شکل را دارد، یک تابع که تابع میگیرد و تابع برمیگرداند.
نما هم معمولا فقط یک تابع در سطح ماژول است. اگر برای فرستادن یک پیامک باید یک کلاینت بسازید، توکن بگیرید، شماره را قالببندی کنید و در پایان خطا را ترجمه کنید، همهاش داخل یک تابع payamak_befrest(shomare, matn) میرود و بقیه برنامه فقط همان یک نام را میشناسد. هیچ کلاسی لازم نیست و کلاس ساختن اینجا فقط یک نام اضافه است.
هزینهای که این پوستهها دارند
هر لایه یک قاب اضافه در گزارش خطا میگذارد. با سه لایه روی هم، سطری که واقعا شکسته چهار سطر پایینتر از جایی است که فکر میکنید، و خواندنش مهارت جداگانهای میخواهد: سطری که مهم است معمولاً سطر اول نیست. هزینه دوم پنهانتر است: پوستهای که فقط بخشی از متدها را پاس میدهد، بقیه را بیصدا حذف میکند، و کدی که آن متدها را صدا میزند با AttributeError میشکند و نه با پیامی درباره پوسته.
قاعده عملی این است که پوسته باید یا کل قرارداد را پاس بدهد یا صریحا قرارداد کوچکتری اعلام کند. همان چیزی که نام بردن یک توانایی، بدون نام بردن پیادهسازی برایش ساخته شده.
جایی که کار گیر میکند، پوشاندن درگاه پرداخت است
تقریبا هر پروژهای در ایران با بیش از یک درگاه پرداخت کار میکند، چون هر بانک و هر واسط پاسخ خودش را دارد. کاری که معمولا اتفاق میافتد این است که کد هر درگاه مستقیم در جای استفاده نوشته میشود و کل منطق سفارش پر از شرط درگاه میشود. مبدل دقیقا برای همین ساخته شده: یک پوسته بهازای هر درگاه، همه با یک متد pardakht(mablagh, kod)، و بقیه برنامه هیچوقت نام هیچ بانکی را نمیبیند. نشانهای که میگوید وقتش رسیده ساده است: نام یک سرویس بیرونی در فایلی پیدا شود که کارش منطق کسبوکار است.
ابزار، و سالی که این حرف درست است
در سال ۲۰۲۶، functools.wraps در کتابخانه استاندارد و رایگان است و همان چیزی است که یک تزیینگر دستنویس بدون آن ناقص میماند: بدون آن، نام و رشته مستندات تابع اصلی گم میشوند و هر ابزاری که به آنها نگاه میکند نام پوسته را میبیند. برای پاس دادن خودکار همه متدها به شی داخلی، __getattr__ در خود زبان هست و جای نوشتن ده متد یکسطری را میگیرد، به این بها که دیگر معلوم نیست پوسته چه چیزی را پشتیبانی میکند.
پرسش بعدی این است که وقتی این شیها ساخته شدند، چه کسی تصمیم گرفت کدام پوسته دور کدام هسته بنشیند، که راههای ساختن یک شی، وقتی سازنده ساده کافی نیست است. و اگر پوستهها بهجای پاس دادن باید به هم خبر بدهند، دسته دیگری لازم است: اینکه شیها چطور با هم حرف میزنند.