الگوهای رفتاری درباره ایناند که شیها چطور به هم خبر میدهند بیآنکه به هم چسبیده باشند، و ناظر پرکاربردترینشان است. پرکاربردترینشان ناظر است و در پایتون دو سطر میشود، چون تابع خودش یک مقدار است:
def negah(self, fn): self._nazerha.append(fn)for fn in self._nazerha: fn(dama)
یک دماسنج با دو ناظر، یکی شوفاژ و یکی نمایشگر. با دو بار تنظیم دمای همان دماسنج، خروجی ثبتشده این است: ['shoofaj khamush', 'namayeshgar 22.0', 'shoofaj roshan', 'namayeshgar 19.5']. دماسنج هیچکدام از آن دو را نمیشناسد و فقط یک فهرست تابع دارد. اضافه کردن ناظر سوم، مثلا ثبت در فایل، هیچ سطری از دماسنج را دست نمیزند.
ترتیب اجرا همان ترتیب ثبت است و این یک جزئیات پیادهسازی نیست، بخشی از قرارداد است. اگر یک ناظر به کاری که ناظر دیگر کرده وابسته باشد، شما یک وابستگی پنهان ساختهاید که هیچجا نوشته نشده و اولین بار که ترتیب ثبت عوض شود میشکند.
دو خطر که همیشه با هم میآیند
اول، خطای یک ناظر بقیه را متوقف میکند. حلقه ساده بالا با اولین استثنا میایستد و ناظرهای بعدی هیچوقت صدا زده نمیشوند. اگر ناظرها مستقلاند، هرکدام باید داخل یک try جدا صدا زده شود و خطایش ثبت شود نه اینکه بالا برود.
دوم، ناظری که هیچوقت برداشته نمیشود زنده میماند. فهرست ناظرها یک ارجاع نگه میدارد، پس شیای که ثبت شده هرگز آزاد نمیشود حتی وقتی هیچجای دیگری به آن اشاره ندارد. در برنامهای که مدتها بالاست همین نشت است، و دلیلش در مقدارها کجا مینشینند و چه وقت آزاد میشوند است. هر متد ثبت باید متد برداشتن هم داشته باشد.
سه الگوی دیگر از همین دسته، و آزمون هرکدام
راهبرد یعنی الگوریتم را بهعنوان آرگومان پاس بدهید؛ در پایتون همان تابعی است که به key میدهید و کلاس لازم ندارد. فرمان یعنی خود کار را بهشکل داده نگه دارید تا بشود صفش کرد یا برگرداندش؛ آزمونش این است که آیا لازم دارید کار را ذخیره کنید. ماشین حالت یعنی رفتار به حالت فعلی وابسته است و بهجای زنجیره شرط، یک نگاشت از حالت به تابع نوشته میشود.
هر سه یک چیز مشترک دارند: زنجیرهای از if را با داده جایگزین میکنند. اگر در کدتان یک زنجیره شرط دارید که هر شاخهاش کار مشابهی میکند، یکی از این سه الگو همان را به یک دیکشنری تبدیل میکند، همانطور که راههای ساختن یک شی، وقتی سازنده ساده کافی نیست برای ساختن انجامش میدهد.
جایی که کار گیر میکند، سیگنالهای فریمورک است
هر کسی که با یک فریمورک وب کار کرده، ناظر را قبلا استفاده کرده بیآنکه اسمش را بداند: هوکها و سیگنالهایی که بعد از ذخیره یک رکورد اجرا میشوند همیناند. مشکلی که در پروژههای واقعی زیاد دیده میشود این است که منطق مهم داخل همین سیگنالها نوشته میشود، و بعد هیچکس با خواندن تابع اصلی نمیفهمد که با ذخیره یک سفارش، پیامک هم میرود. قاعدهای که این را قابل تحمل میکند: ناظر برای کارهای جانبی است، یعنی چیزی که اگر انجام نشود سفارش هنوز درست است. هر چیزی که برای درستی لازم است باید در خود تابع اصلی و بهشکل صریح صدا زده شود.
ابزار، و سالی که این حرف درست است
در سال ۲۰۲۶، برای ثبت ناظر بدون نگه داشتن دائمی شی، weakref.WeakMethod در کتابخانه استاندارد و رایگان است و جای فهرست سادهای را میگیرد که هر چیزی را که یک بار ثبت شد تا آخر عمر برنامه زنده نگه میدارد. برای ماشین حالت، enum.Enum در همان کتابخانه حالتها را از رشتههای آزاد به مقدارهای بسته تبدیل میکند، و همان است که غلط تایپی در نام حالت را به یک خطا تبدیل میکند بهجای یک شاخهای که هیچوقت اجرا نمیشود.
پرسش بعدی این است که وقتی یک ناظر خطا داد، آن خطا کجا برود و چه شکلی داشته باشد، که شکست پرصدا، شکست بیخطر، و فرق این دو جوابش است. و اگر ناظرها بهجای خبر دادن، دور هم میپیچند، ترکیب شیها در شکلهای بزرگتر دسته درست است.