سلام، من محمد حسنی هستم.
من هیچ آموزش و مثال خوب فارسی از design pattern ها پیدا نکردم. هر مقاله شامل یک سری مثال کلی بود که من متوجه نمیشدم دقیقا چه مشکلی حل شده است.
بنابراین سعی میکنم هر آموزشی را که در زمینه الگوهای طراحی میبینم, با مثالهای کاربردی تر بنویسم.
من یک ارائه خوب راجع به design pattern ها دیدم. این نوشته برگرفته از همون ارائه هست.
خب...
فرض کنید ۳ گروه کاربر از ۳ کشور متفاوت داریم: آلمان, هلند, انگلیس. هر کدوم از این کاربران به زبان متفاوتی صحبت میکنند. ما میخواهیم کلاسی بنویسیم که هر کاربر به زبان خودش Hello World را در صفحه کنسول پرینت کند. من از پایتون استفاده میکنم.
در اینجا ۳ کلاس نوشتیم که برای زبان آلمانی, هلندی و انگلیسی. که هر کدام به زبان خود متدی با نام "call me" دارند که جمله "Hello World!" را پرینت میکند.
حالا کد را اجرا میکنیم.
اما مشکل کجاست؟
مشکل در if و else های زیاد در بخش اجرای کد است. اگر من بخواهم یک زبان دیگر اضافه کنم باید یک شرط دیگر هم اضافه کنم. این کد, کد تمیزی نیست. فرض کنید ۲۰۰ زبان داریم. باید ۲۰۰ شرط بنویسیم! و خب… این بده :)
نکته مهم: Design Patternها برای حل یک مشکل استفاده میشوند.
ما برای حل این مشکل از الگوی طراحی Adapter استفاده میکنیم.
قدم اول: برای هر کلاس یک آداپتر تعریف می کنیم. با این کار از شر if و else های زیاد خلاص میشویم.
از خط ۱۶ به پایین، کدها جدید هستند.
همانطور که گفتم، برای هر کلاس یک آداپتر ایجاد کردیم. مثلا کلاس German، یک آداپتر به نام GermanAdapter دارد. در متد init ( در زبان های دیگر به نام constructor شناخته میشود) تعریف کردیم که در زمان ایجاد آبجکت آداپتر باید یک آبجکت از کلاس German(که از این به بعد به آن آبجکت اصلی میگوییم) را به آبجکت آداپتر پاس بدهیم. این آداپتر (و تمام آداپتر های دیگر) متدی به نام call_me دارند. داخل هر متد گفته شده است که زمانی که این متد صدا زده شد باید چه متدی از آبجکت اصلی (که به init پاس داده شده است) صدا زده شود. برای مثال در آداپتر GermanAdapter گفته شده است که متد rufen_sie_mich_an صدا زده شود.
چه مشکلی را حل کردیم؟
۱. همه آداپترها متدی با نام یکسان (call_me) دارند.
۲. if و else ها را حذف کردیم.
اما...
اگر بخواهیم گروه جدیدی اضافه کنیم باید یک آداپتر جدید بنویسیم. و خب این هم بده :)
برای حل این مشکل به سراغ قدم بعد میرویم.
قدم دوم:
از خط ۱۶ به پایین، کدها جدید هستند.
قبل از توضیحات __setattr__ و __getattr__ رو توضیح بدم.
def __init__(self, people, **adapter_methods): self.people = people for key, value in adapter_methods.items(): func = getattr(self.people, value) self.__setattr__(key, func) self._initialized = True
در زمان ساخت آبجکت ابتدا آبجکت اصلی را مشخص میکنیم:
self.people = people
و بعد متدی که دوست داریم صدا زده شود را از آبجکت اصلی میگیریم:
func = getattr(self.people, value)
و به عنوان attribute در آبجکت آداپتر ست میکنیم:
self.__setattr__(key, func)
اما قسمت بعدی کد:
def __getattr__(self, attr): return getattr(self.people, attr) def __setattr__(self, key, value): if not self._initialized: super(PeopleAdapter, self).__setattr__(key, value) else: setattr(self.people, key, value)
در __setattr__ مشخص کردیم که در زمان ساخت آبجکت آداپتر، هر attribute ای که پاس داده بودیم را در خود آبجکت آداپتر ست کن. اما بعد از (وقتی self._initialized = True شد ) همه آبجکتهای جدید رو در آبجکت اصلی ست کن.
چرا؟
چون هر attribute جدیدی که ست شود، هم در آبجکت آداپتر و هم در آبجکت اصلی یک مقدار را خواهند داشت.
برای فهم بهتر این بخش، این کد را در پایین صفحه اجرا کنید:
new_obj = people_adapters[0] new_obj.name = 'mohammad' print(new_obj.name) print(new_obj.people.name) new_obj.people.name = "matin" print(new_obj.name) print(new_obj.people.name) new_obj.people.name = "again mohammad :)" print(new_obj.name) print(new_obj.people.name)
همانطور که میبینید، attribute نام، چه در آبجکت آداپتر و یا در آبجکت کلاس اصلی عوض شود، نتیجه در هر دو آبجکت یکسان است.
و اما قسمت آخر
همانطور که قبلا گفتم
هر زمان هم که یک attribute را فراخوانی کنید و بعد از فراخوانی با ارور روبرو شوید، __getattr__ صدا زده میشود.
این بخش از کد:
def __getattr__(self, attr): return getattr(self.people, attr)
به این دلیل است که بعد از تعریف آبجکت، اگر attribute جدیدی تعریف کنید(مثلا name در مثال بالا) مقدار در آبجکت اصلی ذخیره میشود. بعد از فراخوانی آن در آبجکت آداپتر(new_obj.name) به دلیل عدم وجود این attribute، و به وجود آمدن ارور، متد __getattr__ صدا زده شده و در این متد گفته میشود که "برو و attribute درخواستی را از آبجکت اصلی بخوان". به همین دلیل است که با تغییر مقدار attribute در هر سمت نتیجه یکسان است.
این کد الگوی طراحی آداپتر است که تورو از شر if و else های زیاد خلاص میکنه:
هر الگوی طراحی رو بخونید، کاملا بفهمید و اون رو فراموش کنید! هر زمان که به مشکلی برخورد کردید به خاطرتون میاد که "من یک الگو راجع به این مشکل خونده بودم". به سراغ این مقاله بیایید و کد رو بردارید و استفاده کنید.
لطفا نظرتون رو بیان کنید?
اگه دوست داشتید بیشتر در ارتباط باشیم: https://www.linkedin.com/in/mohidotpy/