بیشتر Design pattern هایی که برای توسعه وب بدردتون میخوره
این مقاله بر اساس زبان python نوشته شده.
امیدوارم مطالب مفیدی ارائه داده باشم.
سر فصل ها
Creational
Factory and AbstractFactory
Singleton
Prototype
Structural
Adapter
Decorator
Proxy and Lazy loader
Behavioral
Observer
State
Strategy
Creational
Factory
در واقع factory همونطور که معلومه برای ساخت یک آبجکت بکار میاد , این pattern برای ساده کردن پیچیدگی های یک کلاس است .
برای مثال ما از یک متد برای چندین کلاس استفاده میکنیم به این کار factory میگن , چون وقتی این نوع متد رو تغییر بدیم توی کل کلاس ها تاثیر می گذارد و تغییر دادن کد بعدا توسط خودمون یا یک برنامه نویس دیگه راحت تر میشه .
چه زمانی باید از الگوی طراحی Factory استفاده کنیم
کلاس هایی داشته باشیم که از آنها شیهای زیادی ساخته میشود. با این کار برنامه نویس دیگر اشیا را ایجاد نخواهد کرد بلکه تمام مسئولیت ایجاد کلاس را به Factory Method واگذار میکند.
قبل از ایجاد شی هایی که نرم افزار با آنها کار میکند، نوع و وابستگی مربوط به آن ها را ندانید.
می خواهید با استفاده از اشیا موجود و جلوگیری از ساختن دوباره آن ها، در منابع سیستم صرفه جویی کنید.
AbstractFactory
در واقع Abstract Factory ترکیبی از چند فکتوری است .
برای مثال وقتی ما در یک کلاس بخواهیم price , detail ,... قرار بدیم تا بتونیم از اونها استفاده کنیم باید کلی شرط if , else بنویسیم اما با abstract Factory کردن اون ها کار خودمونو راحت تر می کنیم .
این pattern پیچیدگی ها رو کمتر و کد نویسی رو راحت تر میکنه.
در کل دیزاین پترن ها در کنار تمرکز بر حل مشکلات برنامه نویسی شی گرا، اهداف متفاوتی را دنبال میکنند. یکی از اهداف اصلی آن ها، افزایش خوانایی کدها و کاهش میزان کدنویسی است. الگوی طراحی کارخانه انتزاعی یا Abstract Factory جزو الگوهای طراحی سازنده (Creational) است که برای مدیریت ساخت اشیا از کلاسها توسعه داده شده است. این الگوی طراحی به شما اجازه میدهد که مجموعه ای از اشیا مرتبط را بدون نیاز به ساخت کلاسهای جداگانه و متعدد ایجاد کنید.
در این مقاله قصد دارم الگوی سینگلتون (Singleton Pattern) را بررسی کنم. Singleton Pattern یک Software Design Pattern است که اجازه ساخت تنها یک instance از class مورد نظر را میدهد.
این الگوی طراحی نرمافزار در مواقعی که حداکثر به یک instance نیاز است استفاده می شود، برای توضیح بهتر از چند مثال واقعی استفاده میکنم. مثلا برنامهای داریم که با یک printer کار میکند و تنها میتوان یک instance از connection با printer داشت، در حالی که ممکن است به روشٰهای مختلف و از بخشهای متفاوتی از برنامه، printer صدا زده شود و نیاز باشد که connection در دسترس قرار گیرد. در اینجا ما میتوانیم از Singleton Pattern استفاده کنیم که تضمین میکند فقط در بار اول یک instance ساخته میشود و دفعات بعدی از همان استفاده میشود. مثال دیگر میتواند برای log زدن در یک برنامه باشد. از هر جای برنامه ممکن هست log زده شود و هر بخشی ممکن است با logger ارتباط برقرار کند، اما اگر همه log ها قرار باشد در یک فایل ذخیره شود و به طور کلی یک logger برای کل برنامه نیاز داشته باشیم، باید این محدودیت در تعداد ساخت instance ایجاد شود وگرنه با هر call، یک instance جدید ساخته میشود و به ازای هر log یک object و فایل جدیدی برای ذخیرهسازی log داریم که اصلا وضعیت مطلوبی نیست و در نتیجه از Singleton Pattern استفاده میشود تا یک instance برای log زدن داشته باشیم و در تمام برنامه از آن استفاده شود. ممکن هست که در ارتباط با دیتابیس هم نیاز به Singleton Pattern باشد تا به ازای هر instance یک connection pool جدید ساخته نشود یا مجبور نشویم که برای هر query از connection جدیدی استفاده کنیم.
الگوی سینگلتون را میتوان با زبانهای مختلف پیاده سازی کرد که در این مقاله ما پیاده سازی آن در python را بررسی میکنیم. در خود python هم میتوان به روشهای مختلفی کد را پیاده سازی کرد و به عنوان decorator یا metaclass از آن بهره برد که ما استفاده از decorator را بررسی می کنیم.
در واقع یکی از اصلی ترین syntax های ایجاد singleton تصویر زیر است.
class Singleton:
@classmethod
def __new__(cls, *args, **kwargs):
if not hasattr(cls, 'instance'):
cls.instance = super(*args, **kwargs)
return cls.instance
*نکته : singleton جزو یکی از بیشترین سوالاتی بوده که درباره desing pattern در مصاحبه های شغلی پرسید شده .
به زبان ساده تر این پترن برای اتصال به دیتابیس زیاد استفاده میشه و به همین دلیل کارایی که داره اینه که فقط , یک راه برای اتصال آبجکت و کلاس میسازه تا حجم کمتری ذخیره شه و وقتی یک بار از یک کلاس استفاده میکنیم برای بار دوم از همون کلاس استفاده کنیم.
*نکته : هیچ وقت از singleton ارث بری (inheritance) نباید کرد, چون ممکنه ماهیت یه کلاس دیگه رو تغییر بده و مثل خودش بکنه . برای مثال:
.
.
Prototype
کپی سطحی یا Shallow copy
Shallow copy توی متغیرهایی(attribute) که یک آدرس حافظه دارن بکار میاد ما در زبان پایتون برای متغیر هایی مثل ((list, dict , (object) , به همراه محتوا داخل اون آبجکت ادرسش رو هم کپی می کنیم .
یعنی وقتی ما یک متغیر dict داریم که داخلش "name" : "amir هست اون میریزیم توی یک متغیر دیگه هم اطلاعاتش کپی میشه هم اونجایی که این save شده
وقتی ما از یک متغیر بکاپ بگیریم آدرسش فرق میکنه با متغیر اول و فقط تمام اطلاعات (values) رو ذخیره میکنه.
روش استفاده از Deep Copy :
Import copy
#use -> copy.deepcopy(variable)
مفهوم اصلی prototype
در واقع prototype برای ساختن نمونه ای از یک چیز برای مثال:
( ساخت نظر سنجی اسنپ ) , وقتی سفر به پایان میرسه بلافاصله از ما یک نظر سنجی میخواد که سوالات مشخص و جواب مشخص شده داره. توی این نوع از prototype استفاده میکنیم , شاید بگید چرا, توی یه سایت فروشگاهی وقتی از یک جنس 5 تا موجود داره بعد خریده شدن همه اجناس اون جنس غیرفعال میشه اما برای یک نظر سنجی همیشه باید یه نمونه از سوالات برای خریدار(user) اماده بشه.
یا یک مثال دیگه :
(خریدن بلیط سینما) , توی بلیط یک سینما اجزای زیادی وجود داره مثل : cinema, movie , start and end time , hall و مهم ترین چیز که دربارش صحبت میکنیم(seat) یا شماره صندلی هست.
برای مثال فضای سالن (hall) تعدادی صندلی داره که میخواد بفروشه , اگه مثل یک فروشگاه باشه بعد فروش صندلی ها اون از بین میره , اما اینطور نیست ما هر بار برای یک فیلم صندلی هارو برای فروش می گذاریم و بعد اون اعلام میکنیم که پر شده نه اینکه از بین رفته (یعنی ما هر بار یک نمونه از صندلی ها رو نشون میدیم).
در کل prototype زیاد تو پروژه ها به کار نمیاد و هر جا احساس کردید که این راه راحت تری هست ازش استفاده کنید ولی این دلیل نمیشه که همه جا ازش استفاده کنید.
Structural
adapter
در واقع کار adapter برای هماهنگی ورودی و خروجی دو interface است منظور از interface یک مفهوم است نه یه امکان زبان پایتون این میتونه تو هر زبان باشه.
فرض کنید خروجی یک دستگاهی باید تبدیل بشه ورودی یا خوراک یه دستگاه دیگه ,اگر به هر دلیلی یکی از این دستگاه ها update بشه کل این مجموعه خراب میشه و دیگه sync نیستن. پس برای اینکه بتونیم دوباره این دوتا رو با هم sync کنیم باید از adapter استفاده کنیم تا این دو به هم وصل شن .
ببینید برای مثال بعضی شارژر ها 3 شاخه است و پریز ما فقط 2 شاخه بهش میخوره . ما سه نوع برای استفاده از این شارژر داریم :
سر شارژر رو ببریم و 2 شاخش کنیم که خیلی خطرناکه .
یا پریز رو , فقط بخاطر این شارژر عوض کنیم که احمقانه ست.
یا از یک آداپتور استفاده کنیم که هزینه کمی داره.
یک مثال . ما یک فروشگاهی داریم که اجناس اون با وجود وضعیت فعلی دلار باید قیمتش بالا پایین شه اون dataintery بک اند سایت باید هر روز 100 تا جنس رو محاسبه کنه تا به قیمت روز بفروشه یا میتونه کلاَِ نفروشه تا وضعیت دلار درست شه که تو ایران امکان پذیر نیست.
یا میتونه همه اون اجناس رو به دلار تبدیل کنه و یک class به نام price adapter بسازه و هر روز فقط قیمت دلار رو وارد کنه و روی همه اجناس تاثیر بزاره برای مثال من یک لباس دارم به قیمت 5 دلار (تبدیل شده به دلار همون روز) به price adapter هم قیمت 50000 تومن میدم. ما میتونیم تمامی اون لباس ها رو در price adapter ضرب کنیم. اینطوری اگه هر روز قیمت دلار رو تغییر بدیم قیمت لباس هم تغییر پیدا میکنه . این روش pattern adapter است.
به syntax زیر توجه کنید نمونه ای از همین مثال بالاست:
در واقع pattern decorator از decorator خود زبان python جداست. pattern decorator میتونه توی همه زبان های برنامه نویسی استفاده شه .که نوع syntax اش هم نسبت به همون زبان فرق میکنه . و در نسخه 2 python چیزی به نام decorator به این زبان اضافه شد یعنی این قابلیت برمیگرده به زمان قدیم.
از pattern decorator برای اضافه کردن یک قابلیت به یک آبجکت یا کلاس استفاده میشه. تقریبا decorator با adapter تفاوت زیادی ندارند اما هر کدام کارایی متفاوتی دارن.
در adapter ما دو آبجکت یا interface رو به هم متصل می کردیم اما توی decorator فقط با یک آبجکت سروکار داریم. و یک نکته ای وجود داره که حتما لزومی نداره برای pattern decorator از خود decorator python استفاده کنیم.
برای مثال به syntax زیر توجه کنید :
from datetime import datetime
def log_time(fn): # function
def internal_function(*args, **kwargs):
start = datetime.now()
results = fn(*args, **kwargs)
end = datetime.now()
print (end - start)
return internal_function
@log_time
def test():
pass
if __name__ == '__main__':
# line before function call
test()
# line after function call
در syntax بالا log_time یک decorator است که محاسبه میکنه مدت زمانی که این فانکشن صدا زده شده تا پایان عملیاتی که انجام داده است. و از decorator python استفاده شده
ما میتونستیم توی خود test اینکار رو انجام بدیم اما اینجوری ماهیت اون کاملا عوض می شد و اگر میخواستیم جایی دیگه از این قابلیت استفاده نکنیم به مشکل می خوردیم .
پس از decorator ها وقتی استفاده میشه که برنامه نویس ها نمی خواهند ماهیت اصلی اون function تغییر کنه .
در کل decorator خیلی شبیه adapter میتونه باشه تو مثال های متفاوت که میشه کار های زیادی باهاش کرد.
مثلا کارفرما میگه مالیات به صورتحسابها در فروشگاه رو محاسبه کن . برای حساب کردن مالیات ما نمیتونیم به ساختار اصلی کد دست بزنیم. پس از decorator استفاده میکنیم تا بدون تغییر دادن کد اصلی اجناس. جداگانه محاسبه رو انجام بدیم.
.
.
.
proxy
در واقع pattern proxy میتونه خیلی شبیه decorator باشه اما با وجود یک سری rule تفاوت بین این دو رو مشخص میشه
از proxy برای کنترل دسترسی ها استفاده میشه . حالا یه وقت هایی میخوایم دسترسی هارو کنترل کنیم یک وقت هایی میخوایم اونا رو از بین ببریم یا کلا دسترسی رو ببندیم
در واقع تفاوت بین decorator و proxy همینجا معلوم میشه . decorator یک سری قابلیت اضافه میکنه یا extend میکنه اما proxy قابلیت ها رو محدود میکنه و چک میکنه اجازه داریم اون فانکشن رو صدا بزنیم یا نه.
وقتی به ما میگن توی یه قطعه کد بگید این pattern decorator یا pattern proxy نباید همون لحظه بگیم decorator چون صدا زده شده باید بریم بررسی کنیم ببینیم که این decorator خود پایتون چه کارایی داره , با توجه به این تشخیص بدیم که pattern decorator یا pattern proxy
@porxy or decorator
برای مثال از اسکریپت decorator استفاده میکنیم به syntax زیر توجه کنید:
def check_permission(func):
def wrapped_func(obj, user):
if obj.user == user:
return func(obj)
print('you don't have access')
return wrapped_func
ما با استفاده از decorator python یک proxy نوشتیم که دسترسی کاربر رو چک میکنه که این محصول مال همون فرد اوله یا نه
@check_permission
def checkout(self):
return 'checkout done'
تو پروژه قبلی ما یک تابع مشخص کردیم که کاربر با وارد کردن user داخل متود checkout برسی میکرد که user که باهاش محصول رو سفارش دادیم با این user که وارد میکنیم همخونی داره یا نه که بهش اجازه بده بنویسه checkout done
print(pur_usa.checkout(user1))
نکته* : حتما لازم نیست برای pattern proxy از decorator python استفاده کرد هرجایی ما یک تابعی برای چک دسترسی یا محدود کردن چیزی استفاده کنیم از pattern proxy استفاده کرده ایم.
برای استفاده از pattern proxy روش های زیادی وجود داره اما 2 روش بیشترین استفاده رو داره
1- استفاده از decorator python است .
2- قابلیت lazy loader که کار محدود کردن رو انجام میده بخاطر همین تو مبحث proxy گذاشتیمش اما ربط زیادی به proxy نداره .
Proxy Lazy Loader
معمولا در بعضی از پروژه ها مقدار داده هایی که از طرف سرور گرفته میشه زیاده .ما امکان داره برای این موضوع از lazy Loader استفاده میکنیم .قبل صدا زدن __init__ و اجرای اون کلاس. بررسی کنیم که امکان استفاده رو داره یا نه .
مثلا ما 3 کلاس مختلف داریم و همونطور که گفتم فرض کنید هرکدومشون داده های زیادی دارند . ما از یکی از این 3 کلاس استفاده ای نداریم . ما نمیتونیم بگیم چون از این کلاس استفاده ای نداریم کلا ابجکتی نسازیم این کاملا بین برنامه نویسان یه اشتباه بزرگ به حساب میاد.
برای حل این مسئله از LazyLoader استفاده میکنیم . حالا چطور :
به syntax زیر توجه کنید :
یک کلاس ساختیم به نام LazyLoader که cls یا هر آبجکتی که بهش میدادی رو میریخت توی object اگر خالی بود و از طریق متد ()getattr میگشت دنبال اون آیتمی که میخواستیم صدا بزنیم این عملیات Lazy Loader است که حتما داخل بدنش از __getattr__ استفاده میشه
برای (responsibility) وظیفه یا مسئولیت یک آبجکت بکار میاد
Observer
در واقع Observer یکی از مهم ترین pattern ها از behavioral است .
وقتی ما میخواهیم رفتار یک آبجکت رو تغییر بدیم و اون تغییر وابسته باشه به یک آبجکت دیگه از observer استفاده میکنیم. در واقع observer زمانی استفاده میشه که ساخته شدن و یا تغییر دادن یک آبجکتی وابسته باشه با آپدیت شدن یک آبجکت دیگه
کجا این اتفاق می افته . برای مثال اسنپ وقتی ما سفر خودمونو پرداخت میکنیم یک سری اتفاقات دیگه به وجود میاد برای مثال push notification هم برای ما میاد که سفر شما پرداخت شد و هم برای راننده میاد که میگه پول سفر پرداخت شد از اون فرد پول نگیر .
تمامی این وابستگی ها با استفاده از observer به وجود اومده که بدون تغییر دادن هر کدام از ماهیت های سفر ارسال نوتیفیکیشن و… اون ها رو به هم متصل کرده.
.
state
این pattern معمولا زیاد توی حوضه وب استفاده نمیشه اما اگر مواقعی نیاز بشه ,خیلی کارگشاست.
کلمه pattern state برای طبقه بندی کردن دسترسی ها است یعنی چی:
برای مثال یک سازمان اداری:
وقتی مشتری(client) یک نامه رو میفرسته به اپراتور , اون شخص نامه رو بررسی میکنه و میفرسته به مقام بالاتر از خودش و این داستان تا خود مدیر اصلی ادامه داره .
این یعنی مشتری یا اپراتور نمیتونند درجا نامه رو بفرستن برای مدیر . به این نوع سلسله مراتب state میگویند
یعنی client -> operator -> supervisor -> manager director
strategy
استراتژی یک الگوی طراحی رفتاری است که مجموعهای از رفتارها را به اشیا تبدیل میکند و آنها را در داخل شی متن اصلی قابل تعویض میکند. ما حالا یعنی چی:
مثالهای استفاده: الگوی استراتژی در کد پایتون بسیار رایج است. اغلب در چارچوبهای مختلف استفاده میشود تا راهی برای کاربران فراهم کند تا رفتار یک کلاس را بدون گسترش آن تغییر دهند.
برای مثال ما داخل پروژه فروشگاه 3 درگاه پرداخت داریم که باید تصمیم گیری بشه که قبل از وارد شدن client به درگاه پرداخت به کدام یکی از درگاه ها ارجا بشه .
توی درگاه پرداخت هم می توانیم دلایل متفاوتی بگذاریم مثلا: