حل لاجیک نسبتا طولانی و پیچیده بدون حتی یک if

خیلی کیف میده اگه یه کاری که در حالت عادی اگه بخواد با ۲۰ تا if و switch case حل بشه رو بدون حتی یک دونه if انجام بدی! چند وقت بود که بحث Functional Programming توی شرکت خیلی زیاد بود و این باعث شد من کلی چیز جدید یاد بگیرم.


اعتبار تصویر از refactoring.guru
اعتبار تصویر از refactoring.guru


مشکل if مگه چیه؟

هیچی! شاید اگه ۲ سال پیش بود من همه این کار رو با if و switch انجام میدادم. مشکل اونجایی بوجود میاد که لازم باشه توی این if و else ها یا switch ها تغییری اتفاق بیوفته. باید تمام اون کد ها یکبار دیگه نوشته بشه، یا یه دونه if ممکنه عملکرد بقیه شرط ها رو تحت تاثیر قرار بده. بنابراین ایده آل تر برای ما اینه که بتونیم تغییرات رو بدون درد و خونریزی انجام بدیم و ویرایش و دستکاری توابع دیگه رو به حداقل برسونیم.


دیزاین پترن ها به درد میخورن

مجید (لید اندروید فیدیبو) برای اینکه بتونیم این مساله رو تمیز تر حل کنیم یک دیزاین پترن معرفی کرد که برای من هم تازگی داشت هم، جوری راه حل موضوع توش پیاده میشد که فقط میتونم بگم زیبا بود. (در جریان باشید که کل این داستان برای یه تایتل مسخره توی صفحه هست 😅 ما با چیزای کوچیک هم خوشحال میشیم و دوست داریم برا خودمون چالش درست کنیم).

دیزاین پترن Decorator یه دیزاین پترن بدردبخور برای اینجور مواقع هست. تعریف و توضیح یه خطی براش میشه این:

فرض کنید یه پیتزا میخواید درست کنید، تو هر مرحله یه چیزی به این پیتزا اضافه میکنید، یه مرحله قارچ، یه مرحله پنیر، یه مرحله سوسیس و همینطوری الی آخر تا در نهایت اون تبدیل بشه به یه پیتزا.

در واقع decorator یعنی همین فرایند. با ترکیبش با مباحثی مثل composition از functional programming میتونه کمک کنه شما لاجیکی رو پیاده کنید که علاوه بر اینکه به شدت منعطف هست و تغییرات عملا بدون هزینه انجام میشه، خوانایی کد رو به شدت افزایش میده.


اصل داستان

توی فرایند توسعه اپلیکیشن جدید فیدیبو یکی از جاهایی که به چنین موردی برخوردم عنوان صفحه مشخصات کتاب بود.

عنوان صفحه باید بر اساس این پارامتر ها ایجاد میشد:

  • نوع محتوا (کتاب، کتاب صوتی، پادکست و ..)
  • قیمت کتاب (رایگان بودن یا نبودن)
  • چند جلدی بودن یا نبودن

و این شروط باهم باید ترکیب هم میشدن، مثلا ممکنه محتوایی داشته باشیم که رایگان باشه از نوع کتاب صوتی و ۳ جلدی. یا پادکست همیشه رایگانه و محتوا هم از جنس صوتی هست.

خب یه راه پیاده سازی این، استفاده از if و switch هست که قبل تر گفتم مشکلی نداره ولی ایجاد تغییرات رو سخت میکنه.

یه راه دیگه استفاده از decorator هاست!

فرض میکنیم این اطلاعات از سرور برای ما ارسال میشه:

https://gist.github.com/hesan-aminiloo/8a664f2da38ca8f234d23ad5153f3523

برای اینکه فقط مفهوم رو توضیح بدم من صرفا با همین ۳ تا دیتا کار دارم.

این کامپوننت من هست در نهایت:

اطلاعات کاملا الکیه! خواستم در جریان باشید
اطلاعات کاملا الکیه! خواستم در جریان باشید


حالا اگر کتاب رایگان باشه:

یا اگر کتاب صوتی باشه و جلد نداشته باشه:


یا ممکنه صوتی باشه و جلد نداشته باشه و رایگان هم نباشه... من دیگه عکس های دیگه نمیذارم. میخواستم فقط بدونید که ترکیب این شرط ها ممکنه خیلی زیاد بشه.


طراحی Decorator ها

خب اگه دقت کرده باشید توی تصاویر، بعضی قسمت های این عنوان ثابت هستن و صرفا بر اساس یک شرط به رشته اصلی اضافه یا کم میشن. دقیقا همون مثال پیتزا!

پس ترکیب decorator های من به این صورت میشه(این ها میشن همون مواد اولیه که برای ساختن پیتزا لازم داریم):

  1. یکی برای اینکه اگه رایگان بود با این جمله شروع کنه: "معرفی و دانلود"
  2. یکی برای اینکه اگه رایگان نبود با این جمله شروع کنه: "معرفی، خرید و دانلود"
  3. یکی برای اینکه اگه رایگان بود کلمه اش رو بنویسه: "رایگان"
  4. یکی برای اینکه اگه تایپ دیتا صوتی بود بنویسه: "کتاب صوتی"
  5. یکی برای اینکه اگه تایپ کتاب بود بنویسه: "کتاب"
  6. یکی برای اینکه اگه تایپ پادکست بود بنویسه: "پادکست"
  7. یکی برای اینکه اگه جلد داشت بنویسه: "جلد X" (این مورد رو من نمینویسم توی این مطلب! دوست داشتید خودتون پیاده اش کنید)

توی این دیزاین پترن یکی از کار هایی که رایج هست برای اسم گذاری توابع اینه که همه decorator ها با کلمه with شروع بشه پس بنابراین ما این decorator ها رو خواهیم داشت:

https://gist.github.com/hesan-aminiloo/1e7d741f973de34c26b6340fc6497742

همینطور که میبینید همه این توابع کاملا pure هستند. آرگیومنت base در واقع رشته ای هست که از خروجی تابع قبل میگیره و هر چی باشه یه چیزی بهش اضافه میکنه و میده بره.


مفهوم Composition

کامپوزیشن در واقع یعنی دادن خروجی فانکشن های دیگه به عنوان ورودی فانکشن بعد و این میتونه تا n مرحله اتفاق بیوفته و در نهایت منتهی میشه به یک تابع نهایی. متاسفم که نتونستم یه عکس خوب برای توضیحش پیدا کنم و قرار بدم.

شرط انجام دادنش اینه که طوری این توابع ورودی و خروجی هاشون تعریف شده باشه (pure باشه) که بتونیم هر زمان دلمون خواست ترتیب شون رو عوض کنیم و تفاوتی توی کار کردنش برامون نداشته باشه.

برای اینکه اون decorator های بالا رو صدا کنیم میتونیم اینطوری عمل کنیم:

https://gist.github.com/hesan-aminiloo/e8a5b5d5a91d560786970fc119931665

خب اینجا دقیقا مفهوم composition داره پیاده میشه ولی به شکل سخت و ناراحتش، تو مثال دوم اول تابع withBook اجرا میشه و خروجیش رو میده به تابع بعد خودش یعنی withFreeContent و مجددا همین اتفاق برای اون میوفته و نتیجه اون میره دست withIntroAndDownload. در نهایت خروجی ما این رشته خواهد بود:

"معرفی و دانلود رایگان کتاب ملت عشق".

الان اگر به هر دلیلی بخواید ترتیب کلمات رو تغییر بدید، یا یه شرط رو حذف کنید یا یه چیزی بهش اضافه کنید کافیه فقط به ترتیب فانکشن رو تو در تو صدا بزنید.

ولی

این مدلی نوشتن یکم سخته دیگه؟ خب چیکار میشه کرد؟! یه تابع compose مینویسیم که لیست توابع رو در قالب یه Array بگیره و از آخر شروع کنه به اجرا کردن و خروجی آخرین تابع رو به عنوان ورودی تابع بعدی بده و همینطوری تا برسه به اولین مقدار این Array. این میشه دقیقا composition با استاندارد ۹۰۰۲.

پس مینویسیمش:

https://gist.github.com/hesan-aminiloo/ba1687a19f57e8284caa6b3bef52635f

در واقع خروجی این تابع یک تابع دیگه است که میتونیم اونو با ورودی دلخواه مون صدا بزنیم.

با این شرایط میتونیم مثال بالا رو این شکلی تغییر بدیم:

https://gist.github.com/hesan-aminiloo/f5d8d95ea6081833db20010bb22ce01a

زیبا نیست؟

نکته مهم اینه که ترتیب نوشتن اسم توابع توی compose اینجا خیلی مهمه. اگه جای یه دونه شون عوض بشه نتیجه یکسان نیست ولی کار میکنه. حالا کافیه این توابع رو داخل یک array بر اساس ورودی هایی که داریم (رایگان بودن یا نبودن یا نوع متحوا و ...) push کنیم و داخل compose به شکل spread قرار بدیم. من این قسمت رو دیگه حوصله ندارم بنویسم ولی ایده رو مطمئنم که گرفتید!




امیدوارم از خوندن این مطلب یه چیزی یاد گرفته باشید. این روش برای من خیلی هیجان انگیز بود و دوست داشتم که با شما هم به اشتراک بذارم. میتونید اگه دوست داشتید از طریق لینکدین باهام در تماس باشید یه اگه نظری دارید خوشحال میشم زیر همین پست بنویسیدشون.