اصل وارونگی وابستگی در اصول SOLID
اصل Dependency Inversion Principle در اصول SOLID چیست؟
Dependency Inversion Principle یا به اختصار DIP که در فارسی به آن «اصل وارونگی وابستگی» یا «اصل معکوس سازی وابستگی» یا «اصل وارونه کردن وابستگی» هم گفته میشود، یک اصل مهندسی نرمافزار و اصل پنجم از اصول طراحی SOLID است.
این اصل بیان میکند که ماژولهای سطح بالا نباید به ماژولهای سطح پایین وابسته باشند. هر دو باید به انتزاعات وابسته باشند. انتزاعات نباید به جزئیات وابسته باشند. جزئیات باید به انتزاعات وابسته باشد.
این بدان معنی است که انتزاعات باید تعاملات بین ماژولها را به جای پیاده سازیهای عینی تعریف کنند.

ماژول سطح بالا چیست؟
ماژولهای سطح بالا، کلاسهایی در یک سیستم نرمافزاری هستند که معماری کلی و تعاملات بین بخشهای مختلف سیستم را تعریف میکنند. این ماژول ها معمولاً مفاهیم سطح بالاتری مانند منطق تجاری، سیاستهای کاربردی و گردش کار را پیاده سازی میکنند و روابط بین اجزای مختلف سیستم را تعریف میکنند.
ماژول سطح پایین چیست؟
ماژول های سطح پایین، کلاسهایی در یک سیستم نرمافزاری هستند که جزئیات فنی خاصی مانند دسترسی به دادهها، ارتباطات شبکه یا ورودی/خروجی فایل را پیاده سازی میکنند. این ماژولها مسئول عملیات اساسی و پایهای سیستم هستند و معمولاً زیرساختهای زیربنایی را برای ماژولهای سطح بالاتر فراهم میکنند و جزئیات خاص مورد نیاز برای کارکرد سیستم را پیادهسازی میکنند.
ماژولهای سطح پایین اغلب به اجزای سختافزاری و نرمافزاری سیستم نزدیکتر هستند و کدهای مربوط به جزئیات را پیادهسازی میکنند که الزامات فنی خاص را پیادهسازی میکند. آنها مسئول انجام وظایف خاصی مانند خواندن یا نوشتن دادهها روی دیسک، برقراری ارتباط با پایگاهداده یا مدیریت ارتباطات شبکه هستند.
انتزاعات چیست؟
انتزاعات به زبان ساده، کلاسهایی هستند که قابل پیادهسازی نیستند اما به عنوان یک طرح و الگو برای سایر کلاسها در نظر گرفته میشوند.
انتزاعات مفاهیم سطح بالا هستند که یک رابط مشترک را برای مجموعهای از موجودیتها یا اجزای مرتبط تعریف می کنند. در مهندسی نرم افزار، انتزاعات به رابطها و قراردادهایی اطلاق میشوند که روابط بین بخش های مختلف یک سیستم را تعریف میکنند.
انتزاعات به ماژولهای سطح بالا و سطح پایین اجازه میدهند تا از طریق یک مجموعه مشترک از رابطهای انتزاعی، به جای پیادهسازی عینی، با یکدیگر تعامل داشته باشند. این اجازه میدهد تا ماژولها از یکدیگر جدا شوند و سیستم را انعطاف پذیرتر و قابل نگهداریتر میکند، زیرا تغییرات یک ماژول به ماژول دیگر مرتبط نمیشود.
جزئیات چیست؟
جزئیات به اجزای خاص سیستم اشاره دارد. نام و ویژگی پراپرتیها و متدها، جزییات یک کلاس هستند و ماژولهای سطح پایین در یک سیستم نرمافزاری را تشکیل میدهند.
جزئیات میتواند شامل مواردی مانند ساختارهای داده، الگوریتمها، عملیات ورودی/خروجی فایل، پروتکلهای ارتباطی شبکه و روشهای دسترسی به پایگاه داده و غیره باشد. آنها مهرهها و پیچ های سیستم هستند و اجرای زیربنایی را برای انتزاعات سطح بالاتر فراهم میکنند.
اصل وارونگی وابستگی کمک میکند تا کدها قابل نگهداری، انعطافپذیرتر و مقیاسپذیرتر شوند. انتزاعات بجای پیادهسازی کد، الگوهایی را تعریف میکنند و ماژولها میتوانند بر اساس نیاز خود در آن الگو، کدها را بر اساس نیاز خود بنویسند، بدون اینکه بر معماری کلی تاثیر بگذارند. به این دلیل است که تغییرات ایجاد شده در سطح پایین به سطح بالا منتقل نمیشود و بالعکس.
یک مثال از اصل وارونگی وابستگی در دنیای واقعی
در دنیای واقعی، اصل وارونگی وابستگی را میتوان در طراحی سیستم گرمایش و سرمایش خانه مشاهده کرد.
ماژول سطح بالا در این سیستم ممکن است معماری کلی را مشخص کند. مثلا طبق این معماری مشخص میکند که انواع دستگاههای کنترل دما که میتوان استفاده کرد، انواع منابع گرمایش و سرمایش، و مقررات بهرهوری انرژی که باید رعایت شوند را تعریف میکند.
ماژولهای سطح پایین در این سیستم، جزئیات خاصی را که برای کارکرد دستگاههای کنترل دما لازم است، مانند ترموستات، واحدهای گرمایش و سرمایش، لولهکشی و… را اجرا میکنند. این ماژولهای سطح پایین برای معماریِ کلی به ماژول سطح بالا وابسته هستند، اما ماژول سطح بالا به جزئیات خاصِ ماژولهای سطح پایین وابسته نیستند.
در این مثال، ماژول سطح بالا انتزاعاتی را ارائه میدهد که تعاملات بین بخشهای مختلف سیستم را تعریف میکند. دستگاه های کنترل دما مانند منابع گرمایش و سرمایش، انتزاعی هستند. ماژولهای سطح پایین جزئیات مورد نیاز برای کارکرد دستگاهها را پیادهسازی میکنند، اما معماری کلی سیستم را دیکته نمیکنند.
با تفکیک ماژولهای سطح بالا و سطح پایین، سیستم گرمایش و سرمایش را میتوان به گونهای طراحی کرد که انعطافپذیر و قابل نگهداری باشد. تغییرات در ماژولهای سطح پایین، مانند افزودن نوع جدیدی از منبع گرمایش، میتواند بدون تأثیرگذاری بر ماژول سطح بالا انجام شود و تغییرات در ماژول سطح بالا، مانند افزودن یک معماری جدید بهرهوری انرژی، میتواند ایجاد شود بدون اینکه بر ماژولهای سطح پایین تاثیر بگذارد.
یک مثال از اصل وارونگی وابستگی در زبان برنامه نویسی
فرض کنید میخواهیم یک سیستم پرداخت را برای سایت طراحی کنیم. در این سیستم پرداخت دو نوع روش پرداخت داریم:
- پرداخت آنلاین (onlinePayment)
- پرداخت کارت به کارت (cardToCard)
پس کد ما به این صورت نوشته میشود:
class onlinePayment {
public function processPayment($amount) {
// Implementation for payment through online payment gateway
}
}
class cardToCard {
public function processPayment($amount) {
// Implementation for payment by card to card method
}
}
class PaymentSystem {
private $paymentMethod;
public function __construct(onlinePayment $paymentMethod) {
$this->paymentMethod = $paymentMethod;
}
public function processPayment($amount) {
$this->paymentMethod->processPayment($amount);
}
}
ماژولهای سطح پایین در این سیستم ممکن است معماری کلی را مشخص کنند، مانند انواع روشهای پرداختی که میتوان استفاده کرد و مقررات امنیتی که باید رعایت شوند.
ماژولهای سطح بالا در این سیستم به ماژولهای سطح پایین وابسته است و در صورتی که بخواهیم روش پرداخت را از «پرداخت آنلاین» به «کارت به کارت» تغییر بدهیم، باید ماژول سطح بالا را ویرایش کنیم.
اگر بخواهیم روش پرداخت جدیدی مانند حواله بانکی اضافه کنیم، باید کلاس PaymentSystem را ویرایش کنیم تا روش پرداخت جدید را مدیریت کند، همچنین هر کد دیگری که به کلاس PaymentSystem وابسته باشد هم باید ویرایش شود. این کار میتواند منجر به تغییرات زیادی در سراسر سیستم شود که نگهداری و تست آن را دشوار میکند.
با استفاده از اصل وارونگی وابستگی، می توانیم از این مشکلات جلوگیری کنیم و یک سیستم انعطاف پذیر و قابلنگهداری طراحی کنیم.
بازنویسی کد بالا با رعایت اصل وارونگی وابستگی:
interface IPaymentMethod {
public function processPayment($amount);
}
class onlinePayment implements IPaymentMethod {
public function processPayment($amount) {
// Implementation for payment through online payment gateway
}
}
class cardToCard implements IPaymentMethod {
public function processPayment($amount) {
// Implementation for payment by card to card method
}
}
در کد بالا، ماژول سطح بالا در این سیستم ممکن است معماری کلی را مشخص کند، مانند انواع روشهای پرداختی که میتوان استفاده کرد و مقررات امنیتی که باید رعایت شوند.
ماژولهای سطح پایین در این سیستم جزئیات خاصی را که برای کارکرد روشهای پرداخت مورد نیاز است، مانند onlinePayment ، cardToCard و حوالههای بانکی را پیادهسازی میکنند. این ماژولهای سطح پایین برای معماریِ کلی به ماژول سطح بالا وابسته هستند، اما ماژول سطح بالا به جزئیات خاص ماژولهای سطح پایین وابسته نیست.
در ماژول سطح بالا یک رابط IPaymentMethod را داریم که دارای یک متد به نام processPayment میباشد. حالا میتوانیم در ماژول های سطح پایین، با توجه به نوعِ روش پرداخت، آن را پیادهسازی کنیم.
در این مثال، ماژول سطح بالا انتزاعیاتی را ارائه میدهد که تعاملات بین بخشهای مختلف سیستم را تعریف میکند. روشهای پرداخت انتزاعی هستند که توسط رابط IPaymentMethod تعریف شدهاند. ماژولهای سطح پایین جزئیات مورد نیاز برای کارکرد روشهای پرداخت را پیادهسازی میکنند، اما ساختار کلی سیستم را تعیین نمیکنند.
با تفکیک ماژول های سطح بالا و سطح پایین، میتوان سیستم پرداخت را به گونه ای طراحی کرد که انعطاف پذیر و قابل نگهداری باشد. تغییرات در ماژولهای سطح پایین، مانند افزودن روش پرداخت جدید، بدون تأثیرگذاری بر ماژول سطح بالا انجام میشود، و تغییرات در ماژولهای سطح بالا، مانند افزودن یک مقررات امنیتی جدید، میتواند بدون تأثیر بر روی ماژول سطح پایین انجام شود.