Design Pattern: توسعه سریع تر، کد تمیزتر، بدون اینکه یادت بره چیکار کردی
حدود ۷ دقیقه مطالعه
دیزاین پترن یکی از اون موضوعاتیه که تو مصاحبهها خیلی میپرسنش، ولی همین تکرار زیاد باعث شده خیلیها فقط اسم ۲۳ تا پترن رو حفظ کنن بدون اینکه بدونن دقیقاً چه مشکلی رو حل میکنن. نتیجهش میشه کدی که پر شده از Factory و Strategy و Observer، در حالی که خود پروژه اصلاً به هیچکدومشون نیاز نداشته.
فرض کن شش ماه پیش یه پروژه رو تموم کردی. امروز مدیرت میگه فقط یه قابلیت کوچیک بهش اضافه کن. پروژه رو باز میکنی، ولی بعد از چند دقیقه میبینی خودتم نمیدونی چرا اون کدها رو اونجوری نوشتی. هر تغییری یه جای دیگه رو میشکنه و انگار پروژه تبدیل شده به یه دومینوی آماده سقوط. اگه این تجربه برات آشناست، احتمالاً همینجاست که به اهمیت واقعی دیزاین پترنها نزدیک میشی.
دیزاین پترن دقیقاً چیه؟
وقتی برنامهنویسها سالها رو پروژههای مختلف کار میکنن، متوجه میشن خیلی از مشکلاتی که باهاش روبرو میشن، در واقع یه مشکل تکراریه که فقط شکلش عوض شده. مثلاً چطور مطمئن بشم فقط یه نمونه از یه کلاس تو کل برنامه وجود داره؟ یا چطور بدون دست زدن به کد قدیمی، یه رفتار جدید به یه شی اضافه کنم؟
دیزاین پترن دقیقاً جواب همین سوالهاست؛ یه راهحل کلی و آزمودهشده، نه یه تیکه کد آماده که کپی پیستش کنی. یه الگوی مفهومیه که نشون میده کلاسها و اشیا باید چطور با هم رابطه داشته باشن تا یه مشکل خاص حل بشه.
این مفهوم رو اولین بار یه کتاب معروف به اسم Design Patterns: Elements of Reusable Object Oriented Software جا انداخت، که چهار نفر نوشتنش و به همین خاطر بهشون میگن Gang of Four یا GoF. قبل از این کتاب، هر تیمی برای این راهحلها یه اسم جدا میذاشت. GoF اومد این الگوها رو اسمگذاری و مستند کرد، و همین باعث شد برنامهنویسهای سراسر دنیا یه زبون مشترک برای صحبت دربارهی طراحی پیدا کنن.
چرا اصلاً باید وقت بذاریم واسه یادگیریشون
- وقتی میگی «من از Observer استفاده کردم»، طرف مقابل فوری میفهمه ساختار کد چجوریه، بدون اینکه لازم باشه خط به خط بخونتش.
- مشکلی که الان داری حلش میکنی، احتمالاً قبلاً هزاران بار توسط بقیه حل شده. چرا دوباره از صفر شروع کنی؟
- خیلی از این پترنها دقیقاً واسه این طراحی شدن که وقتی نیاز جدید اومد، مجبور نباشی کد قدیمی رو دستکاری کنی.
- کدی که رو یه پترن شناختهشده بنا شده، هم واسه بقیه راحتتر خونده میشه، هم واسه خودت تو شش ماه آینده.
Design Pattern با Architecture چه فرقی داره
این سوالی هست که تقریباً همهی تازهکارها میپرسن. Architecture تصمیمیه که رو کل سیستم تاثیر میذاره؛ مثلاً اینکه پروژهت Microservices باشه یا Monolith. دیزاین پترن اما رو یه مقیاس خیلی کوچیکتر عمل میکنه، یه کلاس یا چند تا کلاس مرتبط. به زبون ساده، Architecture میگه ساختمونت چند طبقه باشه و اتاقها کجا باشن، ولی دیزاین پترن میگه هر در چجوری باز و بسته بشه.
بریم سراغ خود پترنها
پترنهای کلاسیک GoF تو سه دسته جا میگیرن: ساختی، ساختاری، و رفتاری. بذار چندتاشون رو با کد نشون بدم تا دستت بیاد قضیه از چه قراره.
۱. Creational (پترنهای ایجادی یا سازنده — به نحوهی ساخته شدن اشیا مربوطن)
Singleton احتمالاً معروفترین و در عین حال بحثبرانگیزترین پترنه. کارش اینه که مطمئن بشه فقط یه نمونه از یه کلاس تو کل برنامه وجود داره، مثلاً واسه یه اتصال به دیتابیس یا یه سرویس Config:
class Database:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
هر بار که Database() رو صدا بزنی، همون نمونهی قبلی رو بهت برمیگردونه، نه یه نمونهی جدید.
Factory Method منطق ساخت شی رو از کد اصلی جدا میکنه. مثلاً یه سیستم پرداخت که بسته به نوع درگاه، آبجکت مناسب رو میسازه:
class PaymentFactory:
def create_payment(self, method):
if method == "paypal":
return PayPalPayment()
elif method == "stripe":
return StripePayment()
Decorator بهت اجازه میده یه قابلیت جدید به یه شی اضافه کنی بدون اینکه ساختار اصلیش رو تغییر بدی. دقیقاً همون کاری که میدلورهای Express انجام میدن:
app.use(logger); // یه لایه رفتار جدید
app.use(authenticate); // یه لایه دیگه
app.use(rateLimiter); // و یکی دیگه
هر Middleware یه لایه رفتار به Request اضافه میکنه، بدون اینکه به کد اصلی Request دست بزنه.
Adapter هم یه چیز خیلی رایجه؛ وقتی یه کتابخونهی قدیمی خروجی XML میده ولی بقیه سیستمت با JSON کار میکنه، یه Adapter میذاری وسط که این ناسازگاری رو حل کنه. Facade هم مشابهشه، فقط به جای سازگار کردن دو تا اینترفیس، یه اینترفیس ساده رو میذاره جلوی یه سیستم پیچیده — مثل یه کلاس VideoConverter که پشت صحنه دهها کلاس پیچیده رو مدیریت میکنه ولی خودش فقط یه متد ساده داره.
۳. Behavioral (پترنهای رفتاری — به نحوهی ارتباط بین اشیا مربوطن)
Strategy بهت اجازه میده الگوریتم رو تو زمان اجرا عوض کنی، بدون اینکه کلاس اصلی رو دستکاری کنی:
class RoutingStrategy:
def calculate_route(self, start, end):
raise NotImplementedError
class WalkingRoute(RoutingStrategy):
def calculate_route(self, start, end):
# منطق مسیریابی پیاده
pass
class DrivingRoute(RoutingStrategy):
def calculate_route(self, start, end):
# منطق مسیریابی با ماشین
pass
Observer هم شاید پرکاربردترین پترن رفتاری باشه. وقتی وضعیت یه شی تغییر میکنه، خودکار به بقیه خبر میده — دقیقاً همون مکانیزمی که پشت نوتیفیکیشن اینستاگرام یا تلگرامه. با هر پست جدید، همهی فالوورها مطلع میشن، بدون اینکه پستکننده لازم باشه بدونه دقیقاً کیا دارن گوش میدن.
بقیهی پترنها هم همین منطق رو دارن: Builder واسه ساخت اشیای پیچیده مرحله به مرحله (مثل ساخت یه Request تو Retrofit)، Command واسه تبدیل یه درخواست به یه شی مستقل (مثل دکمهی Undo تو ادیتورها)، State واسه تغییر رفتار بر اساس وضعیت داخلی (مثل وضعیت سفارش آنلاین: در حال پردازش، ارسالشده، لغوشده)، و Iterator واسه پیمایش یه مجموعه بدون افشای ساختار داخلیش (همون کاری که for-of تو جاوااسکریپت انجام میده).
کی این پترنها رو کنار بذاریم
این بخش شاید مهمتر از خود پترنها باشه. خیلی از برنامهنویسهای تازهکار، بعد از یادگیری این الگوها، سعی میکنن همهجا ازشون استفاده کنن. این کار معمولاً نتیجهی عکس میده.
اگه یه کلاس ساده کارت رو راه میندازه، لازم نیست بپیچیش تو یه Factory یا Strategy. اگه هدفت از استفادهی یه پترن اینه که خودتو حرفهای نشون بدی نه اینکه یه مشکل واقعی حل کنی، احتمالاً داری کد رو واسه بقیه سختتر میکنی. اگه پروژهت یه اسکریپت یکبار مصرف یا یه MVP سریعه، این سرمایهگذاری اضافه شاید هیچوقت به بازده نرسه. و اگه فریمورکی که داری استفاده میکنی از قبل این پترن رو تو خودش داره (خیلی از فریمورکهای مدرن Dependency Injection یا Observer رو بیلتاین دارن)، دوباره پیادهسازیش کردن دستی فقط تکرار کاره.
در کل یه قانون مهم که باید درنظر بگیری: این پترنها رو وقتی جدی بگیر که کدت داره به جایی میرسه که هر تغییر کوچیک چند جای دیگه رو هم میشکنه، یا وقتی میدونی چیزی میسازی که قراره تو آینده گسترش پیدا کنه. تا اون موقع، ساده نوشتن معمولاً بهتر از over-engineering کردنه.
اگه دیزاین پترنها رو صرفاً یه لیست ۲۳ تایی اسم عجیب ببینی، خیلی زود فراموششون میکنی. ولی اگه هر پترن رو جواب یه مشکل واقعی در نظر بگیری، کمکم میبینی خیلی از فریمورکها و پروژههای بزرگی که هر روز باهاشون کار میکنی، دقیقاً رو همین ایدهها ساخته شدن.
