۱۳۸۸/۰۹/۲۷

تزريق وابستگي (dependency injection) به زبان ساده


اين مطلب در ادامه‌ي "آشنايي با الگوي IOC يا Inversion of Control (واگذاري مسئوليت)" مي‌باشد كه هر از چندگاهي يك قسمت جديد و يا كاملتر از آن ارائه خواهد شد.

==============
به صورت خلاصه ترزيق وابستگي و يا dependency injection ، الگويي است جهت تزريق وابستگي‌هاي خارجي يك كلاس به آن، بجاي استفاده مستقيم از آن‌ها در درون كلاس.
براي مثال شخصي را در نظر بگيريد كه قصد خريد دارد. اين شخص مي‌تواند به سادگي با كمك يك خودرو خود را به اولين محل خريد مورد نظر برساند. حال تصور كنيد كه 7 نفر عضو يك گروه، با هم قصد خريد دارند. خوشبختانه چون تمام خودروها يك اينترفيس مشخصي داشته و كار كردن با آن‌ها تقريبا شبيه به يكديگر است، حتي اگر از يك ون هم جهت رسيدن به مقصد استفاده شود، امكان استفاده و راندن آن همانند ساير خودروها مي‌باشد و اين دقيقا همان مطلبي است كه هدف غايي الگوي تزريق وابستگي‌ها است. بجاي اين‌كه هميشه محدود به يك خودرو براي استفاده باشيم، بنابر شرايط، خودروي متناسبي را نيز مي‌توان مورد استفاده قرار داد.
در دنياي نرم افزار، وابستگي كلاس Driver ، كلاس Car است. اگر موارد ذكر شده را بدون استفاده از تزريق وابستگي‌ها پياده سازي كنيم به كلاس‌هاي زير خواهيم رسيد:

//Person.cs
namespace DependencyInjectionForDummies
{
class Person
{
public string Name { get; set; }
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private Car _myCar = new Car();

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
new Driver().DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

توضيحات:
كلاس شخص (Person) جهت تعريف مسافرين، اضافه شده؛ سپس كلاس خودرو (Car) كه اشخاص را مي‌توان به آن اضافه كرده و سپس به مقصد رساند، تعريف گرديده است. همچنين كلاس راننده (Driver) كه بر اساس ليست مسافرين، آن‌ها را به خودروي خاص ذكر شده هدايت كرده و سپس آن‌ها را با كمك كلاس خودرو به مقصد مي‌رساند؛ نيز تعريف شده است. در پايان هم يك كلاينت ساده جهت استفاده از اين كلاس‌ها ذكر شده است.
همانطور كه ملاحظه مي‌كنيد كلاس راننده به كلاس خودرو گره خورده است و اين راننده هميشه تنها از يك نوع خودروي مشخص مي‌تواند استفاده كند و اگر روزي قرار شد از يك ون كمك گرفته شود، اين كلاس بايد بازنويسي شود.

خوب! اكنون اگر اين كلاس‌ها را بر اساس الگوي تزريق وابستگي‌ها (روش تزريق در سازنده كه در قسمت قبل بحث شد) بازنويسي كنيم به كلاس‌هاي زير خواهيم رسيد:

//ICar.cs
using System;

namespace DependencyInjectionForDummies
{
interface ICar
{
void AddPassenger(Person p);
void Drive();
}
}

//Car.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Car : ICar
{
//همانند قسمت قبل
}
}

//Van.cs
using System;
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Van : ICar
{
List<Person> _passengers = new List<Person>();

public void AddPassenger(Person p)
{
_passengers.Add(p);
Console.WriteLine("{0} added!", p.Name);
}

public void Drive()
{
foreach (var passenger in _passengers)
Console.WriteLine("Driving {0} ...!", passenger.Name);
}
}
}

//Driver.cs
using System.Collections.Generic;

namespace DependencyInjectionForDummies
{
class Driver
{
private ICar _myCar;

public Driver(ICar myCar)
{
_myCar = myCar;
}

public void DriveToMarket(IList<Person> passengers)
{
foreach (var passenger in passengers)
_myCar.AddPassenger(passenger);

_myCar.Drive();
}
}
}

//Program.cs
using System.Collections.Generic;
using System;

namespace DependencyInjectionForDummies
{
class Program
{
static void Main(string[] args)
{
Driver driver = new Driver(new Van());
driver.DriveToMarket(
new List<Person>
{
new Person{ Name="Ali" },
new Person{ Name="Vahid" }
});

Console.WriteLine("Press a key ...");
Console.ReadKey();
}
}
}

توضيحات:
در اينجا يك اينترفيس جديد به نام ICar اضافه شده است و بر اساس آن مي‌توان خودروهاي مختلفي را با نحوه‌ي بكارگيري يكسان اما با جزئيات پياده سازي متفاوت تعريف كرد. براي مثال در ادامه، يك كلاس ون با پياده سازي اين اينترفيس تشكيل شده است. سپس كلاس راننده‌ي ما بر اساس ترزيق اين اينترفيس در سازنده‌ي آن بازنويسي شده است. اكنون اين كلاس ديگر نمي‌داند كه دقيقا چه خودرويي را بايد مورد استفاده قرار دهد و از وابستگي مستقيم به نوعي خاص از آن‌ها رها شده است؛ اما مي‌داند كه تمام خودروها، اينترفيس مشخص و يكساني دارند. به تمام آن‌ها مي‌توان مسافراني را افزود و سپس به مقصد رساند. در پايان نيز يك راننده جديد بر اساس خودروي ون تعريف شده، سپس يك سري مسافر نيز تعريف گرديده و نهايتا متد DriveToMarket فراخواني شده است.
به اين صورت به يك سري كلاس اصطلاحا loosely coupled رسيده‌ايم. ديگر راننده‌ي ما وابسته‌ي به يك خودروي خاص نيست و هر زماني كه لازم بود مي‌توان خودروي مورد استفاده‌ي او را تغيير داد بدون اينكه كلاس راننده را بازنويسي كنيم.
يكي ديگر از مزاياي تزريق وابستگي‌ها ساده سازي unit testing كلاس‌هاي برنامه توسط mocking frameworks است. به اين صورت توسط اين نوع فريم‌ورك‌ها مي‌توان رفتار يك خودرو را تقليد كرد بجاي اينكه واقعا با تمام ريز جرئيات آن‌ها بخواهيم سروكار داشته باشيم (وابستگي‌ها را به صورت مستقل مي‌توان آزمايش كرد).