اگر NET. کار هستید باید بدانید(قسمت سوم)->وابستگی و اصل D از SOLID

در قسمت دوم ما اصل L از اصول SOLID رو بررسی کردیم.در این قسمت به بررسی اصل D یا همون Dependency Inversion میپردازیم. که به فارسی میشه معکوس سازی وابستگی ها.

اصل D، در یک تاپیک مهمی در نرم افزار، تحت عنوان مدیریت وابستگی یا Dependency management، مطرح شده که به ارتباط اجزا با همدیگه میپردازه. مدیریت وابستگی این سوال رو مطرح میکنه، که Component ها یا اجزای درون یک پروژه چطور به هم وصل بشوند، تا ما کد مون در مقابل تغییرات، Flexible یا منعطف تر باشه.

اینکه میگیم اتصال بین Component یا اجزا، از دو بعد بررسی میشه:

  • یک بعدش اینه که در یک سلوشنی، پروژه های مختلفی هستند و ما چطوری این پروژه ها رو اصطلاحا Wired-Up یا سیم بندی کنیم که در زمان بیلد کردن سلوشن، سیکل یا Cycle ای نداشته باشیم. جوری که پروژه A، رفرنس به B داشته باشه و B به C و باز C به A و اینطوری کل سلوشن اگر بخواد بیلد بشه،هر پروژه معطل بیلد شدن پروژه دیگه باشه.
  • یک بعد دیگه اش اینه که اصولا چه Concrete کلاس هایی، چه اینترفیس هایی میتوانند داخل یک پروژه در کنار هم قرار بگیرند.چه متر و معیاری باید وجود داشته باشه که ما بخوایم کلاس ها رو در پروژه های گوناگون یا در یک پروژه در کنار هم قرار بدیم؟ چون در نهایت پروژه ها تبدیل به پکیج میشوند. حالا Package در دات نت، همون فایل Assembly به شکل dll هست و در جاوا Jar file.

که این خودش دنیایی از بحث رو در مورد Package Cohision و Package Couppling مطرح میکنه. ولی من همه سعی ام رو میکنم که تیکه تیکه وارد بحث ها بشم تا رشته کار از دستمون خارج نشه.

"کانکریت کلاس(Concrete class) به کلاسی گفته میشه که متد هاش همگی پیاده سازی داره و میشه ازش شی ایجاد کرد."

معماری سه لایه -> Big Ball Of Mud

بیشتری از ماها که تجربه کاری بالای 6 سال در مارکت نرم افزار رو داریم، با یک طراحی رایج قدیمی که به عنوان معماری سه لایه شناخته میشد آشنایی داریم.

معماری سه لایه
معماری سه لایه

در این مدل، اینطور فرض میشد که :

  • من یک لایه با abstraction یا انتزاع بالا دارم، که لایه ی Presentation من هست. گاها بهش لایه UI هم میگن.که در واقع همون واسط کاربری که من ساختم و متشکل شده از گرید ها و تولبارها و ... که به کاربرم نشون میدم.
  • پشت این لایه یک لایه ی Business دارم که Logic یا منطق برنامه ام توش هست. و لایه ی Presentation من به dll یا همون اسمبلی لایه ی بیزنس رفرنس داره. دلیلشم واضحه!. چون برای رخداد های پشت دکمه های تولبار و نمایش داده ها درون گریدش و ...، باید متد ازش فراخوانی کنه.
  • و در نهایت یک لایه ی پایین تر دارم، که لایه Low level تر یا سطح پایینه منه، که به بانک داده وصله و از بانک اطلاعات میگیره و وظیفه این لایه، تنظیم Data Type های پراپرتی های درون کلاس های لایه Business، سرویس دهی برای عملیات CRUD و ... است. و اینجا هم لایه ی Business به dll لایه ی Data Access رفرنس داره.

اگر نحوه ی Reference دهی رو در این مدل نگاه کنیم، میبینیم که از بالا به پایینه. یعنی کامپوننت High level داره Low level رو صدا میزنه، برای گرفتن خدمات یا سرویس.

ولی آیا Flow یا جریان تغییرات در برنامه هم به همین شکل هست؟

یعنی چی Flow یا جریان تغییرات؟ منظورم اینه که اگر در لایه ای من تغییری ایجاد کنم، این تغییر به لایه ی پایین ترش منتشر میشه و اونجا رو تحت تاثیر قرار میده؟ یا نه اثر تغییراتی که دادم رو در لایه ی بالایی میبینم؟

جواب مسما نه هست. جریان تغییرات شبیه به جریان رفرنس از بالا به پایین نیست.

چون در واقع تغییر در لایه ی دیتا، که Low level ترین لایه برنامه است، میتونه تا High level ترین لایه هم گسترش پیدا کنه. و این یعنی برنامه ما در معرض ریسک بالایی برای تغییرات هست. چون لایه ای مثل Business که سطح بالا محسوب میشه، و ارزش بالایی هم داره چون منطق برنامه عموما اونجاست و پیچیدگی اشم به مراتب بالاتره از لایه ی پایینی هست، به راحتی میتونه توسط لایه ی پایینی خودش Data، تحت تاثیر قرار بگیره.

حالا همین تغییر هم از دو منظر هست.

یعنی خود تغییر دادن در یک لایه از دو جنبه میتونه باشه:

یک وقتی تغییرات Implementation ای هست، مثلا من میام یک dll جدید به رفرنس های لایه ی Data اضافه کنم و از اون دی ال ال در متدهای این لایه استفاده میکنم و Using میکنم. اینطوری لایه ی بالایی خودم یعنی Business رو وادار کنم که این dll رو یا بشناسه. یا بهش رفرنس بده.

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

این متد برای انجام محاسباتش، احتیاج به پارامتری تحت عنوان Rate داره که از بانک داده خونده میشه. لذا یک شی از کلاسی در لایه دیتا new میکنم و از متد GetRate اون شی برای خوندن عدد Rate ام استفاده میکنم.

حالا فردا روزی من میام در API این متد GetRate تغییر ایجاد میکنم. مثلا میام به پارامترهاش یک چیزی اضافه میکنم. یا ترتیب پارامترهاش رو بهم میزنم یا نوع خروجی اش رو تغییر میدم و ....

همین تغییر ساده، لایه ی Business من رو میتونه دچار تغییرات عدیده و گاها باگ در چندین نقطه حساس بیزینس برنامه بکنه.

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

و این خیلی بده.

بریم در ادامه ببینیم چطور میتونیم وابستگی هامون رو معکوس کنیم.

دو مفهوم SAP و SDP در Dependency management

مدیریت وابستگی مفهومی رو در دل خودش داره تحت عنوان stability یا پایداری.یعنی چی؟

اگر عکس پایین رو نگاه کنید، از دیدگاه Dependency management ، الان Z از A پایدارتر یا Stable تره. چرا؟ منطقش اینه که تغییر دادن Z سخت تره! چون اگر Z تغییر کنه، A و B و C به نحوی بهش وابسته هستند و تحت تاثیر قرار میگیرند.پس Z باید Stable باشه.

پایداری یا Stability رو که اگر یک عددی بین 0 تا 1 فرض کنیم. خونه پایین اش یعنی 0 میشه حداکثر پایداری و خونه بالاش یعنی 1، میشه حداقل پایداری.

حالا چطور به این عدد برسیم؟ خیلی ساده است.

I = OUT / (IN + OUT)

میزان Dependency یا وابستگی خروجی یک کامپوننت، تقسیم بر میزان Dependency یا وابستگی ورودی + خروجی.

الان Z هیچ وابستگی از خارج به خودش نداره.(هیچ Referency از بیرون خودش نگرفته). ولی 3 تا وابستگی به خودش داره(یعنی سه جا خودش Reference داده شده و واردشون شده) که A و B و C هستند.پس عدد I برای Z میشه 0. حداکثر پایداری.

پس در مقابل Stability، ما مفهومی تحت عنوان Flexibility داریم(بعضی منابع بهش میگن InStability) که یعنی حداقل پایداری که منظورمون کامپوننتی هست، که درونش تغییرات زیاد اتفاق می افته.

حالا فرض کنید Z به O که Flexible هست وابسته باشه مثل تصویر بالا. اینجا اولین جایی که اصل Dependency Inversion چراغش قرمز میشه و مفهوم Stable Dependency Principle یا SDP رو مطرح میکنه.

یک کامپوننت Flexible نباید یک کامپوننت Stable بهش وصل باشه. چون پایداریش در معرض تهدید واقع میشه.

حالا سوالی که مطرح میشه اینه که من چطور میتونم مانع از این وابستگی خطرناک بشم. بالاخره من به O نیاز دارم.نمیتونم که بگم برو گمشو!! Dependency inversion چه راهکاری پیش روی من میگذاره؟

در واقع راه حل ساده اینه که من بیام کاری کنیم که این وابستگی از جنس تبعیت باشه. یعنی چی؟

یعنی یک اینترفیس معرفی کنم. بعد به کامپوننت Flexible برنامه یعنی O بگم که از اون اینترفیس ارث ببر. به کامپوننت Stable هم بگم تو هم از اینترفیس استفاده کن. حالا O ناگزیر هست که از Contract اینترفیس من پیروی کنه و Z هم دیگه ارتباط مستقیم با O نداره.

این میشه همون مفهوم تبعیت که من میگم.

اینترفیس در واقع abstraction یا انتزاعی هست که Dependency inversion به عنوان راهکار معرفی میکنه. و هدفش سست کردن ارتباطات(Loose coupling) برای ماژول های Stable هست که توسعه پذیر بودن برنامه رو بالاتر ببره.

برعکس شدن وابستگی رو میبینی؟

حالا که abstraction یا نقش انتزاع در Dependency inversion مطرح شد. مفهوم Stable Abstraction Principle یا SAP رو هم مطرح میکنم.

این مفهوم یا اصل میگه که هر چی یک کامپوننت Stable تر باشه. میزان abstraction یا انتزاعش هم به همون نسبت بالاتره. به زبان ساده تر، یعنی تعداد کلاس های abstract و اینترفیس ها در اون کامپوننت، از تعداد کلاس های Concrete به مراتب بالاتره.

جالبه بدونین که abstraction هم مثل Stability یک عدد محاسباتی بین 0 و 1 هست.

که از تقسیم میزان کلاس های abstract و اینترفیس، به تعداد کل کلاس های اون کامپوننت به دست میاد.

A = AbstractFiles / TotalFiles

عدد 1، یعنی حداکثر انتزاع و 0 یعنی حداقل انتزاع.

نمودار بالا، ارتباط بین Abstraction و InStability هست که به سه قسمت تقسیم میکنه.

ناحیه بالا که Uselessness که یعنی منطقه بلا استفاده.یعنی کامپوننت در حداکثر انتزاع و حداقل پایداری هست. مثل این میمونه که من یک کامپوننت دارم نه به جایی Reference داره و نه از جایی Reference گرفته
و واسه خودش سیلون و حیرون توی پروژه هست!

ناحیه پایین Zone of Pain یا قسمت خطرناکه ماجراست. که Abstraction در سطح پایینی هست و Stability در حد بالا. یعنی چی؟ یعنی کامپوننت من پر از Concrete کلاس هست و به خیلی جاها هم Reference داره. پس هر تغییری در این کامپوننت، من رو وادار خواهد کرد که خیلی جاها رو عوض کنم. و این یعنی برنامه نویس دردش میاد!! چون با هر تغییر، توی دردسر بزرگی می افته.

پس با این تفاسیر، بهترین و ارزشمند ترین کامپوننت ها، اونهایی هستند که کاملا از این دو ناحیه دور باشند. این کامپوننت ها اونهایی هستند که باب مارتین بهشون میگه ترتیب اصلی یا Main Sequence.

فاصله از این ترتیب اصلی، باید شما رو نسبت به کامپوننت خودتون مشکوک کنه.و نیازتون رو به Dependency inversion بررسی کنید.


در قسمت های بعدی الگوی طراحی Dependency Injection رو مطرح خواهیم کرد که در مورد تزریق وابستگی به کلاس های Concrete سطح بالاست و چگونگی انجام آن.