این قانون حاکی از آن است که ماژولهای سطح بالا هرگز نباید به ماژولهای سطح پایین وابسته باشند. Dependency Inversion Principle یا به اختصار DIP دولوپر را موظف میسازد تا کلیهٔ ماژولها (کلاسها) را بر مبنای Abstraction (انتزاع) توسعه دهند که در ادامه سعی میکنیم با ذکر مثالی از دنیای واقعی، کاربرد این اصل را توضیح دهیم (لازم به یادآوری است که مفهوم Dependency Inversion نباید با Dependency Injection اشتباه گرفته شود چرا که این دو کاملاً با یکدیگر فرق دارند.)
حال برای آنکه DIP را در عمل ببینیم، پروژهای به صورت زیر ایجاد میکنیم:
dependency-inversion-principle/
├── Content.php
├── DBConnectionInterface.php
├── index.php
├── MongoDBConnection.php
└── MySQLConnection.php
لازم به یادآوری است که برای شلوغ نشدن سورسکدهای مورد استفاده در این آموزش، از نوشتن کلیهٔ تگهای آغازین php؟>
خودداری کردهایم. به منظور روشن شدن کاربرد DIP، ساختار پروژهٔ زیر را مد نظر قرار میدهیم:
ابتدا پوشهای به نام dependency-inversion-principle
ایجاد کرده سپس فایلی تحت عنوان Content.php
کلاسی تحت عنوان Content
به صورت زیر ایجاد میکنیم:
class Content
{
private $dbConnection;
public function __construct(MySQLConnection $dbConnection)
{
$this->dbConnection = $dbConnection;
}
}
یک پراپرتی تحت عنوان dbConnection$
داریم که به محض ساخت یک آبجکت از روی این کلاس، از طریق کاسنتراکتور مقداردهی میشود. پارامتر ورودی این کانستراکتور مصداق عینی Dependency Injection است به طوری که این کلاس وابسته به کلاسی تحت عنوان MySQLConnection
است. همچنین لازم به یادآوری است که MySQLConnection
یک ماژول سطح پایین است در حالی که کلاس Content
یک ماژول سطح بالا تلقی میگردد و از آنجا که Content
متکی بر MySQLConnection
است، این کلاس ناقض اصل DIP میباشد زیرا اگر در آینده بخواهیم دیتابیس را تغییر دهیم، باید کلاس Content
را نیز ریفکتور کنیم که همین کار ناقض اصل Open-Close Principle است.
در حقیقت، کلاس Content
اصلاً نباید دغدغهٔ این را داشته باشد که وب اپلیکیشن ما از چه دیتابیسی استفاده میکند که برای رفع این مشکل، هم ماژول سطح بالا یا Content
در این پروژه و هم ماژول سطح پایین یا MySQLConnection
هر دو مبتنی بر یک اینترفیس (انتزاع) باشند که برای این منظور، در فایلی تحت عنوان DBConnectionInterface.php
اینترفیسی با نام DBConnectionInterface
به صورت زیر خواهیم ساخت:
interface DBConnectionInterface
{
public function connect();
}
حال فایلی به نام MySQLConnection.php
میسازیم و داخلش کلاسی به اسم MySQLConnection
ایجاد میکنیم که از این اینترفیس ایمپلیمنت میکند:
require_once 'DBConnectionInterface.php';
class MySQLConnection implements DBConnectionInterface
{
public function connect()
{
return "mysql is connected";
}
}
در ادامه، کلاس Content
را به صورت زیر ریفتکتور میکنیم:
require_once 'DBConnectionInterface.php';
class Content
{
private $dbConnection;
public function __construct(DBConnectionInterface $dbConnection)
{
$this->dbConnection = $dbConnection;
}
public function showConnection()
{
return $this->dbConnection->connect();
}
}
به عبارتی، به جای این که مستقیماً MySQLConnection
را به کانستراکتور کلاس Content
پاس دهیم، آبجکتی از جنس اینترفیس DBConnectionInterface
را به عنوان پارامتر ورودی در نظر میگیریم که چیزی کاملاً انتزاعی است به طوری که اگر در آینده به جای سیستم مدیریت پایگاه دادهٔ MySQL بخواهیم به مثلاً MongoDB وصل شویم، بدون نیاز به تغییر کلاس Content
این کار امکانپذیر خواهد بود. برای تست این موضوع، فایلی تحت عنوان index.php
میسازیم و آن را به صورت زیر تکمیل میکنیم:
require_once 'Content.php';
require_once 'MySQLConnection.php';
$db = new MySQLConnection();
$content = new Content($db);
echo $content->showConnection();
به عنوان خروجی خواهیم داشت:
mysql is connected
حال فرض کنیم قصد داریم موتور دیتابیس از از MySQL به MongoDB تغییر دهیم. برای این منظور، فایلی تحت عنوان MongoDBConnection.php
ساخته و کلاسی به اسم MongoDBConnection
داخل آن میسازیم:
require_once 'DBConnectionInterface.php';
class MongoDBConnection implements DBConnectionInterface
{
public function connect()
{
return "mongodb is connected";
}
}
فایل index.php
را هم به صورت زیر تغییر میدهیم:
require_once 'Content.php';
require_once 'MongoDBConnection.php';
$db = new MongoDBConnection();
$content = new Content($db);
echo $content->showConnection();
به عنوان خروجی داریم:
mongodb is connected
میبینیم بدون آنکه هیچگونه تغییری در کلاس Content
دهیم، میتوانیم موتورهای دیتابیس مختلفی را در وب اپلیکیشن خود مورد استفاده قرار دهیم.
جمعبندی
DIP دولوپر را موظف میسازد تا ماژولهای سطح بالا را درگیر جزئیات نکند و در مقابل، ماژولها و کلاسهای سطح پایین باید وارد جزئیات شوند اما در عین حال هر دو مورد باید مبتنی بر اصل انتزاع باشند. در پاسخ به این پرسش که «فایدهٔ DIP چیست؟» میتوان گفت تبعیت از این قانون در توسعهٔ نرمافزار به صورت شیئگرا این امکان را در اختیار ما قرار میدهد تا بخشهای مختلف نرمافزارمان اصطلاحاً Decoupled (مستقل) باشند و همین مسئله به ما کمک میکند تا بتوانیم از ماژولهای مختلف بدون دغدغهٔ وابستگی آنها به سایر ماژولها در جایجای نرمافزار خود استفاده نماییم.