کانتینرهای تزریق وابستگی در جاوا اسکریپت – از صفر تا صد


جاوا اسکریپت به جهت ماهیت انعطافپذیر خود امکان استفاده از تکنیکهای مختلفی را فراهم میسازد. در این مقاله به بررسی کانتینرهای تزریق وابستگی در جاوا اسکریپت خواهیم پرداخت. این الگو در عمل همان هدف تزریق وابستگی را اما به روشی انعطافپذیر و قدرتمند ارائه میکند و کانتینرها به عنوان میزبانهای وابستگیها تابع (یا کلاس) عمل میکنند که در موارد نیاز مانند مرحله مقداردهیشان به آنها دسترسی پیدا میکنیم.
تزریق وابستگی بدون کانتینر
در این بخش به مرور سریع ماهیت تزریق وابستگی، شیوه نمایش آن در کد، مشکلاتی که حل میکند و مشکلاتی که از آنها رنج میبرد خواهیم پرداخت. تزریق وابستگی الگویی است که به ما کمک میکند تا از هاردکد کردن وابستگیها در ماژولها جلوگیری کنیم و به فراخوانی کننده این امکان را بدهیم که آنها را تغییر داده و در صورت نیاز موارد مورد نظر خود را ارائه کند.
این وابستگیها میتوانند در مرحله سازنده (وهلهسازی) تزریق شوند و یا در ادامه آنها را با یک متد setter تزریق کنیم:
این روش برخی مشکلات دارد که در ادامه آنها را توضیح میدهیم.
مشکل شماره 1
اگر لازم باشد شیوه ساخته شدن Toad را تغییر دهیم و از چیزی شکننده مانند موقعیتیابی آرگومانها یا ساختمان داده آنها استفاده کنیم، باید کد را به صورت دستی تغییر دهیم، زیرا در بلوک کد هاردکد شده است.
به عنوان مثال این سناریو در زمانی اتفاق میافتد که بخواهیم یک «تغییر ناسازگار» (breaking change) در کلاس ایجاد کنیم. اگر یک پارامتر سوم مانند weight به سازنده Frog اضافه کنیم، چنین اتفاقی رخ میدهد:
در این صورت Toud باید بهروزرسانی شود، زیرا این وابستگی جدید در مرحله وهلهسازی Frog اضافه شده است:
بنابراین اگر آن را به همین شیوه حفظ کنیم، به طور مکرر باید با هر تغییری که در پروژه رخ میدهد اقدام به بهروزرسانی کلاس Frog بکنیم که به طور بدیهی وضعیت نامطلوبی است.
مشکل شماره 2
باید هر بار بدانیم کدام وابستگی برای Toad استفاده شده است. مشکل دیگر این رویکرد آن است که باید بدانیم Toad هم اکنون به چهار آرگومان دقیقاً به همان ترتیب که در وهله Frog مقداردهی شده است نیاز دارد و حتی باید انواع داده آنها را نیز بدانیم، چون در غیر این صورت به آسانی باگ ایجاد میشود.
این وضعیت در صورتی که بدانیم Toad اساساً یک Frog است بغرنجتر میشود چون با دانستن این نکته ممکن است به صورت تصادفی تصور کنید که Toad باید Forg را بسط داده باشد.
از این رو درک میکنیم که به جای آن یک وهله از Frog درون Toad ایجاد شده است و اینک همه چیز با هم مخلوط میشود زیرا شما یک انسان هوشمند هستید و کدی که مقابل شما قرار دارد با دنیای واقعی همخوانی ندارد.
مشکل شماره 3
مشکل بعدی رویکرد فوق، گنجاندن کد غیر ضروری بیشتر است. و با استفاده از الگوی تزریق وابستگی، این مشکلها از طریق معکوس کردن کنترل روش وهلهسازی از وابستگیها حل میشود:
این کد آسانی است. اکنون زمانی که تغییر ناسازگار دیگری در Frog رخ دهد، مثلاً آرگومانها درون شیء جاوا اسکریپت قرار گیرند، دیگر حتی لازم نیست به Toad مراجعه کنیم یا وقت خود با خواندن Toad و سپس Frpg و سپس بازگشتن به Toad و همین طور تا آخر به هدر بدهیم.
دلیل این امر آن است که اینک میتوانیم بخشی که یک وهله از Toad را ایجاد میکند تغییر دهیم. این وضعیت بسیار بهتر از این است که مجبور باشیم وارد قضایا شویم و کد مربوط به پیادهسازی Toad را تغییر دهیم که رویه بدی محسوب میشود.
دیگر لازم نیست در مورد شیوه ساخته شدن frog نگران باشد و باید تنها بداند که frog را به عنوان یک آرگومان میگیرد و آن را در مشخصه frog. برای استفادههای آتی ذخیره میکند. اکنون مسئولیت وابستگیهای آن را بر عهده میگیرید:
بنابراین برخی رویههای کدنویسی تمیز را با جدا کردن جزییات پیادهسازی Frog از سازنده Toad تمرین کردهایم. این کار به این جهت مطلوب است که دیگر لازم نیست toad در مورد شیوه ساخته شدن Frog اطلاع داشته باشد و هر چه که باشد میتواند آن را بسط دهد.
الگوی کانتینر تزریق وابستگی (DIC)
اکنون که مفاهیم تزریق وابستگی را با هم مرور کردیم، نوبت آن رسیده که در مورد کانتینر تزریق وابستگی صحبت کنیم. سؤال این است که چرا به الگوی DIC نیاز داریم و چرا تزریق وابستگی بدون کانتینر در برخی سناریوهای پیچیده به تنهایی کافی نیست؟
مشکل اینجا است که این رویه مقیاسپذیر نیست. هر چه پروژه بزرگتر شود، اعتماد به نگهداری کد در بلندمدت کاهش مییابد، زیرا در طی زمان شلوغتر میشود. به علاوه، باید ترتیب تزریقهای وابستگی را نیز در ترتیب معینی حفظ کنید، به طوری که با مشکل undefined شدن یک بخش در زمان وهلهسازی بخش دیگر مواجه نشوید. بنابراین اساساً شش ماه بعد، کد ما به چیزی مانند زیر تبدیل میشود:
کل فرایند adoption زمانی که setAdoption از FrogAdoptionFacility فراخوانی میشود، پایان مییابد. بیاید فرض کنیم که شروع به توسعه کد با استفاده از این کلاسها کردهایم و در نهایت به نسخهای مانند زیر میرسیم:
اگر این کد را اجرا کنیم، کار میکند و یک شیء adoption میسازد که به صورت زیر است:
اینک یک اپلیکیشن کاملاً زیبا داریم که یک واگذاری frog را انجام میدهد و مشتریان میتوانند دران یک frog به دست آورند. اما فرایند adoption صرفاً یک تراکنش پولی دادن/گرفتن ساده نیست.
فرض میکنیم که قانونی وجود دارد که الزام میکند این فرایند برای هر adoption یک FROG به مالکان جدید باید اجرا شود. بنابراین لازم است که قراردادی ایجاد شده و امضای مشتری در آن وارد شود. در ادامه یک پروانه نیز ایجاد میشود که مشتریان باید برای حفاظت حقوقی خود داشته باشند. در نهایت adoption کامل میشود.
به کلاس FrogOwner زیر توجه کنید:
این کلاس سه وابستگی به صورت frogOwner، frogOwnerLicense و frog دارد. فرض کنید یک بهروزرسانی در مورد frogOwner وجود دارد و به وهلهای از Client تبدیل میشود:
اکنون فراخوانیهای مقداردهی FrogParadiseOwner باید بهروزرسانی شوند. اما اگر FrogParadiseOwner را در سراسر کد در چندین جا مقداردهی کنیم چه اتفاقی میافتد؟ اگر کد طولانیتر شود و تعداد این وهلهها افزایش یابد، مشکل نگهداری نمود بیشتری مییابد.
این همان جایی است که کانتینر تزریق وابستگی میتواند موجب بهبود شود، زیرا در این حالت تنها باید کد در یک موقعیت تغییر یابد. بنابراین کانتینر تزریق وابستگی به صورت زیر خواهد بود:
اینک به جای مقداردهی مستقیم آن مانند قبل و الزام به تغییر دادن همه وهلههای دیگر کد:
میتوانیم از DIC برای یکبار بهروزرسانی آن اقدام کنیم و دیگر نیازی به تغییر همه بخشهای کد نداریم، زیرا جهتگیری گردش برای آن کانتینر معکوس شده است:
اینک کاری که DIC انجام میدهد را توضیح میدهیم. ما کلاسها یا تابعهایی را که میخواهیم از سوی DIC حل شوند را با ارسال به متد.factory() درج میکنیم که در نهایت در مشخصه.factory ذخیره میشوند:
در مورد هر کدام از این تابعها که به ()factory. ارسال میشوند باید آرگومانهایشان با استفاده از ()register ثبت شوند تا بتوان در زمان مقداردهی تابع درخواستی از سوی کانتینر مورد استفاده قرار گیرند. این آرگومانها از مشخصه dependencies. دریافت میشوند. با استفاده از متد ()dependencies. میتوانید چیزهایی به وابستگیها اضافه کنید:
زمانی که بخواهید چیزی را بازیابی کنید، میتوانید از get. به همراه نوعی key استفاده کنید. این متد از KEY برای گشتن به دنبال dependencies استفاده میکند و در صورتی که چیزی پیدا کند آن را بازگشت میدهد. در غیر این صورت به دنبال factories میگردد و در صورتی که چیزی پیدا کند با آن مانند یک تابع رفتار میکند که باید resolve شود.
سپس اجزا را به inject. میسپارد که نامهای وابستگیهای (آرگومانها را خوانده و آنها را از مشخصه dependencies. میگیرد و تابع را اجرا کرده و آرگومانها را تزریق میکند و نتیجه را بازگشت میدهد.
ما در مثال کدمان، از parse-function استفاده کردهایم تا به متد inject امکان بدهیم که نامهای آرگومان یک تابع را به دست آورد. برای این که این کار را بدون کتابخانه انجام دهیم، میتوانیم آرگومانهای دیگری به get. اضافه کنیم کرده و به صورت زیر به inject. آن ارسال کنیم:
در هر حال، نتیجه مشابهی به دست میآید:
به این ترتیب به پایان این مقاله میرسیم. امیدواریم از این راهنما بهره لازم را برده باشید.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی
- آموزش JavaScript ES6 (جاوا اسکریپت)
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- معرفی جاوا اسکریپت ناهمگام — به زبان ساده
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
==