سرفصل‌های آموزشی
آموزش قوانین SOLID
درآمدی بر قانون Dependency Inversion

درآمدی بر قانون Dependency Inversion

این قانون حاکی از آن است که ماژول‌های سطح بالا هرگز نباید به ماژول‌های سطح پایین وابسته باشند. 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 (مستقل) باشند و همین مسئله به ما کمک می‌کند تا بتوانیم از ماژول‌های مختلف بدون دغدغهٔ وابستگی‌ آن‌ها به سایر ماژول‌ها در جای‌جای نرم‌افزار خود استفاده نماییم.

online-support-icon