mohammad hasani
mohammad hasani
خواندن ۴ دقیقه·۳ سال پیش

Design Pattern| قسمت اول: adapter

Adapter Design Pattern
Adapter Design Pattern


سلام، من محمد حسنی هستم.
من هیچ آموزش و مثال خوب فارسی از design pattern ها پیدا نکردم. هر مقاله شامل یک سری مثال کلی بود که من متوجه نمی‌شدم دقیقا چه مشکلی حل شده است.
بنابراین سعی می‌کنم هر آموزشی را که در زمینه الگوهای طراحی می‌بینم, با مثال‌های کاربردی تر بنویسم.
من یک ارائه خوب راجع به design pattern ها دیدم. این نوشته برگرفته از همون ارائه هست.

خب...
فرض کنید ۳ گروه کاربر از ۳ کشور متفاوت داریم: آلمان, هلند, انگلیس. هر کدوم از این کاربران به زبان متفاوتی صحبت می‌کنند. ما میخواهیم کلاسی بنویسیم که هر کاربر به زبان خودش Hello World را در صفحه کنسول پرینت کند. من از پایتون استفاده می‌کنم.

https://gist.github.com/MohammadHasani/340679b401d0b24aa6e897ce31756f7a

در اینجا ۳ کلاس نوشتیم که برای زبان آلمانی, هلندی و انگلیسی. که هر کدام به زبان خود متدی با نام "call me" دارند که جمله "Hello World!" را پرینت می‌کند.
حالا کد را اجرا می‌کنیم.

https://gist.github.com/MohammadHasani/6db62359659b73c55d1298f407f8905a

اما مشکل کجاست؟

مشکل در if و else های زیاد در بخش اجرای کد است. اگر من بخواهم یک زبان دیگر اضافه کنم باید یک شرط دیگر هم اضافه کنم. این کد, کد تمیزی نیست. فرض کنید ۲۰۰ زبان داریم. باید ۲۰۰ شرط بنویسیم! و خب… این بده :)

نکته مهم: Design Patternها برای حل یک مشکل استفاده می‌شوند.
ما برای حل این مشکل از الگوی طراحی Adapter استفاده می‌کنیم.

قدم اول: برای هر کلاس یک آداپتر تعریف می کنیم. با این کار از شر if و else های زیاد خلاص می‌شویم.

از خط ۱۶ به پایین، کدها جدید هستند.

https://gist.github.com/MohammadHasani/d38f96621e1692af43c33f6451466725

همانطور که گفتم، برای هر کلاس یک آداپتر ایجاد کردیم. مثلا کلاس German، یک آداپتر به نام GermanAdapter دارد. در متد init ( در زبان های دیگر به نام constructor شناخته می‌شود) تعریف کردیم که در زمان ایجاد آبجکت آداپتر باید یک آبجکت از کلاس German(که از این به بعد به آن آبجکت اصلی می‌گوییم) را به آبجکت آداپتر پاس بدهیم. این آداپتر (و تمام آداپتر های دیگر) متدی به نام call_me دارند. داخل هر متد گفته شده است که زمانی که این متد صدا زده شد باید چه متدی از آبجکت اصلی (که به init پاس داده شده است) صدا زده شود. برای مثال در آداپتر GermanAdapter گفته شده است که متد rufen_sie_mich_an صدا زده شود.

چه مشکلی را حل کردیم؟
۱. همه آداپتر‌ها متدی با نام یکسان (call_me) دارند.
۲. if و else ها را حذف کردیم.


اما...

اگر بخواهیم گروه جدیدی اضافه کنیم باید یک آداپتر جدید بنویسیم. و خب این هم بده :)
برای حل این مشکل به سراغ قدم بعد می‌رویم.

قدم دوم:
از خط ۱۶ به پایین، کدها جدید هستند.

https://gist.github.com/MohammadHasani/f225ce7e557e2e1d8f1893292a4cda05




قبل از توضیحات __setattr__ و __getattr__ رو توضیح بدم.

  • هر زمان یک attribute را به یک آبجکت اختصاص بدهید، متد __setattr__ صدا زده می‌شود.
  • هر زمان هم که یک attribute را فراخوانی کنید و بعد از فراخوانی با ارور روبرو شوید، __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 = &quotmatin&quot print(new_obj.name) print(new_obj.people.name) new_obj.people.name = &quotagain mohammad :)&quot 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 در هر سمت نتیجه یکسان است.

tl;dr

این کد الگوی طراحی آداپتر است که تورو از شر if و else های زیاد خلاص می‌کنه:

https://gist.github.com/MohammadHasani/f225ce7e557e2e1d8f1893292a4cda05


نکته مهم:

هر الگوی طراحی رو بخونید، کاملا بفهمید و اون رو فراموش کنید! هر زمان که به مشکلی برخورد کردید به خاطرتون میاد که ‌"من یک الگو راجع به این مشکل خونده بودم". به سراغ این مقاله بیایید و کد رو بردارید و استفاده کنید.


لطفا نظرتون رو بیان کنید?
اگه دوست داشتید بیشتر در ارتباط باشیم: https://www.linkedin.com/in/mohidotpy/

pythondesign patternپایتونadapterبرنامه نویسی
شاید از این پست‌ها خوشتان بیاید