خیلی وقتها Design Pattern اشتباه نیست؛
خیلی وقتها Design Pattern اشتباه نیست؛ ما فقط در زمان اشتباه سراغش میرویم. مثلاً چون تازه با Strategy آشنا شدهایم، سعی میکنیم هر if/else ای را با آن جایگزین کنیم. در حالی که…
قسمت ۱ از ۳
بخش ۱
خیلی وقتها Design Pattern اشتباه نیست؛ ما فقط در زمان اشتباه سراغش میرویم.
مثلاً چون تازه با Strategy آشنا شدهایم، سعی میکنیم هر if/else ای را با آن جایگزین کنیم.
در حالی که شاید آن بخش از کد فقط به یک تابع ساده نیاز داشته باشد، نه چند کلاس و Interface جدید.
اصل ماجرا این نیست که چند Pattern بلدیم.
مهم این است که بفهمیم:
این کد الان واقعاً چه مشکلی دارد؟
قبل از انتخاب Pattern، اول مسئله را درست پیدا کنیم.
قبل از اینکه بگوییم:
«اینجا Factory بزنیم» یا «Strategy بهتره»
یک سؤال ساده بپرسیم:
دقیقاً کجای این کد اذیتمان میکند؟
مثلاً این Constructor را ببینید:
const orderService = new OrderService(
database,
paymentService,
smsService,
logger,
cache,
notificationService
);
مشکل این کد چیست؟
تعداد پارامترها زیاد شده، ترتیب آنها مهم است و شاید مشخص نباشد کدام وابستگی ضروری یا اختیاری است.
در چنین شرایطی، یک Factory میتواند ساخت آبجکت را سادهتر کند:
const orderService =
OrderServiceFactory.create();
حالا تمام جزئیات ساخت داخل Factory قرار گرفته است.
نکته مهم این است:
ما بهخاطر علاقه به Factory از آن استفاده نکردیم؛ بهخاطر یک درد واقعی از آن استفاده کردیم.
یک Design Pattern زمانی ارزش دارد که یک مشکل تکراری را کمتر کند.
مثلاً فرض کنید در چند جای پروژه مستقیماً به API پیامک وصل شدهایم:
await axios.post(
"https://sms-provider.com/send",
{
mobile: phone,
text: message
}
);
این کد داخل Controller، Job و Serviceهای مختلف تکرار شده است.
حالا اگر شرکت پیامک عوض شود، باید چندین فایل را تغییر دهیم.
راه بهتر این است که ارتباط با سرویس پیامک را پشت یک رابط ثابت قرار دهیم:
await smsService.send(
phone,
message
);
داخل smsService میتوانیم از هر سرویسدهندهای استفاده کنیم:
class SmsService {
async send(phone, message) {
return smsProvider.send(
phone,
message
);
}
}
حالا اگر سرویسدهنده تغییر کند، فقط یک بخش از پروژه تغییر میکند.
اینجاست که Pattern واقعاً هزینه تغییر را کم میکند.
برای انتخاب Design Pattern مناسب، لازم نیست همه Patternها را حفظ باشیم.
فقط این سه سؤال را بپرسیم:
۱. مشکل در ساختن آبجکتهاست؟
مثلاً:
new ReportService(
database,
logger,
cache,
sms,
notification
);
اینجا شاید Factory یا Builder کمک کند.
۲. مشکل در ارتباط بین بخشهای سیستم است؟
مثلاً همهجای پروژه مستقیماً به یک API خارجی وابسته شده است:
axios.post(
"external-api.com/send",
data
);
اینجا یک Adapter میتواند ارتباط را ساده کند:
externalService.send(data);
۳. مشکل در رفتارهای متغیر است؟
مثلاً کد به این شکل درآمده:
if (paymentType === "card") {
payByCard();
} else if (
paymentType === "wallet"
) {
payByWallet();
} else if (
paymentType === "credit"
) {
payByCredit();
}
در چنین شرایطی، Strategy میتواند هر روش پرداخت را جدا کند:
const strategy =
paymentStrategies[paymentType];
strategy.pay();
اسم دستهبندیها مهم نیست.
مهم این است که بفهمیم مشکل از ساختن است، از ارتباط است یا از رفتار.
بعضی وقتها فکر میکنیم هرچه Pattern بیشتری در پروژه استفاده کنیم، کد حرفهایتر است.
مثلاً برای یک محاسبه ساده چنین ساختاری میسازیم:
class DiscountStrategy {
calculate(price) {
return price * 0.9;
}
}
class DiscountFactory {
static create() {
return new DiscountStrategy();
}
}
در حالی که شاید کل نیاز پروژه همین باشد:
const calculateDiscount =
price => price * 0.9;
نسخه دوم سادهتر، خواناتر و قابلفهمتر است.
هر Pattern با خودش هزینه میآورد:
کلاس بیشتر، فایل بیشتر، لایه بیشتر، و زمان بیشتر برای فهمیدن کد.
پس قبل از اضافهکردن هر Pattern بپرسیم:
این Pattern دقیقاً قرار است کدام مشکل را حل کند؟
اگر جواب روشنی نداریم، احتمالاً هنوز به آن نیاز نداریم.
پترن یعنی پیچیدگی را در یک نقطه کنترل کنیم؛ نه اینکه بدون دلیل، پیچیدگی جدیدی بسازیم.
خلاصه چیز هایی که گفتیم میشه توی این عکس خلاصه کرد.
فعلا برای پارت یک اینا رو داشته باشیم، بخش های بعدی داخل هر قسمت عمیق تر میشیم.
