علیرضا ارومند
علیرضا ارومند
خواندن ۶ دقیقه·۴ سال پیش

فصل یازدهم Clean Architecture - معکوس‌سازی وابستگی

1. مقدمه:

اصل وارونگی وابستگی یا Dependency Inversion Principle که از این به بعد به آن DIP می‌گوییم، به بیان این مطلب می‌پردازد که بیشترین انعطاف در سیستم‌ها زمانی به دست می‌آید که موقع نیاز به یک وابستگی، از طریق یک قرارداد این وابستگی تامین شود نه یک پیاده سازی خاص.

هنگامی که از یک زبان برنامه نویسی استاتیک مثل C# یا جاوا استفاده می‌کنیم، نیاز داریم که وابستگی‌های سیستم را به آن معرفی کنیم و این تعریف وابستگی باید فقط از طریق ماژول‌هایی انجام شود که انواع قرارداد‌ها داخل آن وجود دارد، مثل پروژه‌هایی که شامل Interfaceها است. در هیچ جای پروژه نباید وابستگی از طریق پیاده‌سازی تامین شود.

این قانون در زبان‌های داینامیکی مثل روبی و پایتون نیز وجود دارد، در این زبان‌ها هم وابستگی فقط باید از طریق قراردادها تامین شود. هر چند در این زبان‌ها تمایز بین قراردادها و پیاده سازی‌ها کمی چالش برای ما ایجاد می‌کند.

واضح است که در نظر گرفتن این اصل به عنوان یک قانون کلی غیر واقع‌گرایانه است. به هر حال برای توسعه سیستم‌های نرم‌افزاری نیاز داریم با پیاده‌سازی‌های مختلفی کار کنیم. برای مثال String در جاوا و C# برای کار کردن با رشته‌ها کاربرد دارد، پیاده سازی دقیقی داشته و تلاش برای حذف آن امکان پذیر نیست و نباید برای حذف این وابستگی تلاشی انجام شود.

دقیق‌تر اگر نگاه کنیم، کلاس String بسیار با ثبات است. سالها برای طراحی و پیاده سازی آن زمان صرف شده‌است. این پیاده‌سازی با دقت بسیار بالایی انجام شده. درچنین کلاس‌هایی تغییرات نیز با وسواس خیلی زیادی انجام می‌شود. ثبات این کلاس در این حد بالاست که طراح سیستم نیازی به نگرانی در مورد تغییرات احتمالی آن ندارد.

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

در اصل هنگام پیاده سازی باید از وابستگی به پیاده‌سازی‌هایی که دائم در حال تغییر هستند و ثبات خاصی ندارند دوری کنیم. این اصل در مورد چنین وابستگی‌هایی صدق می‌کند. در اصل ماژول‌هایی که دائم در سیستم ما در حال تغییر و توسعه هستند باید به عنوان وابستگی در نظر گرفته شوند و برای آن‌ها از قرارداد‌ها استفاده کنیم.

2. قراردادهای باثبات و پایدار:

با هر تغییر در یک Interface باید تمامی کلاس‌هایی که آن را پیاده سازی کرده‌اند تغییر کنند. اما برعکس این مورد صادق نیست. یعنی با هر تغییر در کلاس‌های پیاده سازی کننده یک Interface لزومی به تغییر در Interface نیست. همینجا برای ما آشکار می‌شود که قرارداد‌ها پایدارتر از پیاده‌سازی‌ها هستند.

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

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

به کلاس‌ها و پیاده سازی‌ها با تغییرات زیاد وابسته نباشید: در صورت نیاز با استفاده از Interfaceها وابستگی‌ها را تامین کنید. این قانون در مورد تمامی زبان‌ها فارغ از داینامیک یا استاتیک بودن آن‌ها صدق می‌کند. این قانون استفاده از Abstract Factory را برای نمونه سازی بسیار تقویت می‌کند.

از کلاس‌های با تغییرات زیاد ارث بری نکنید: این قانون با در نظر گرفتن شرایطی خاص با قانون قبلی مرتبط است. در زبان‌های برنامه‌نویسی استاتیک، این نوع وابستگی بسیار سفت و سخت است کاملا در سورس‌کد وابستگی ایجاد می‌کند و باید با احتیاط به سراغ آن برویم. در زبان‌های داینامیک اما این وابستگی به محکمی زبان‌های استاتیک نیست. با این حال از آنجایی که وابستگی وابستگی است، باید حتی المقدور از آن دوری کنیم.

توابع کلاس‌های والد را بازنویسی نکنید: شاید در ظاهر به نظر برسد با بازنویسی یک تابع وابستگی به آن از بین می‌رود، اما در واقع کماکان داریم از آن کلاس ارث بری می‌کنیم و این وابستگی در سورس کد پنهان شده است. به جای این کار باید توابع پیاده سازی را انتزاعی سازی کرده و در هرجایی با توجه به نیاز اصلی خود پیاده سازی را انجام دهید.

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

3. کارخانه‌ها (Factories):

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

در بسیاری از زبان‌های برنامه‌نویسی مثل C# و جاوا برای مدیریت فرایند نمونه سازی و رعایت قواعد بالا از Abstract Factory استفاده می‌کنیم.

در تصویر زیاد نحوه انجام کار را مشاهده می‌کنید. Application از ConcreteImp که پیاده‌سازی Service
است استفاده می‌کند. برای این کارApplication باید نمونه‌ای از ConcreteImp ایجاد کند. ببه این منظور و برای دوری از وابستگی به سورس‌کد Application متد makeSvc از قرارداد ServiceFactory را صدا می‌زند. پیاده‌سازی ServiceFactory نمونه‌ای از ConcreteImp ایجاد کرده و به عنوان Services بازگشت می‌دهد.

استفاده از الگوی Abstract Factory برای مدیریت وابستگی
استفاده از الگوی Abstract Factory برای مدیریت وابستگی

خطی که در تصویر مشاهده می‌کنید و سیستم را به دو قسمت تقسیم کرده است، مرز معماری است. تمامی وابستگی‌هایی که در تصویر مشاهده می‌کنید و از خط عبور کرده است هم جهت هستند و همه به سمت قرارداد اشاره می‌کنند. این خط سیستم را به دو قسمت قرارداد‌ها و پیاده‌سازی‌ها تقسیم می‌کند. تمامی قوانین سطح‌بالا و ماندگار سیستم در قسمت بالایی و وابستگی‌های پر تغییر که جزئیات پیاده‌سازی را نگهداری می‌کنند در قسمت پایین قرار گرفته‌اند.

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

4. اجزاء واقعی ( Concrete Components):

اجزاء پیاده‌سازی شده در قسمت پایین تصویر یک وابستگی دارند که اصل DIP را رعایت نمی‌کند. البته این طبیعی است و نمی‌توان تمامی وابستگی‌ها را به طور کامل از بین برد. به کمک این اصل می‌توانیم وابستگی‌های سیستم را به حداقل رسانده و آن‌ها را در بخش‌هایی خاص از سیستم محدود کنیم.

اغلب سیستم‌های نرم‌افزاری شامل چنین بخشی هستند که اغلب با عنوان main شناخته می‌شوند. به این خاطر که این بخش‌ها شامل تابع main می‌شوند. این تابع نمونه‌ای از ServiceFactoryImpl را ایجاد می‌کند و آن را در متغییری عمومی از جنس ServiceFactory قرار می‌دهد. در ادامه Application از این متغیر عمومی برای دسترسی به Factory استفاده می‌کند.

جمع بندی:

هرچقدر در این مجموعه به جلو می‌رویم و اصول سطح بالاتری از معماری را بررسی می‌کنیم، با DIP بارها و بارها مواجه خواهیم شد. یکی از قابل مشاهده‌ترین اصل‌های سازماندهی در دیاگرام‌های ما DIP خواهد بود. خطی که در تصویر مشاهده کردید در فصل‌های آینده تبدیل به مرز معماری می‌شود. نحوه عبور وابستگی‌ها از آن خط بالا در یک جهت و به سمت موجودات انتزاعی‌تر ، به یک قانون جدید تبدیل خواهد شد که آن را "قانون وابستگی" می‌نامیم.

software architectureclean architecturesoliddependency inversion principledip
شاید از این پست‌ها خوشتان بیاید