۱۳۸۸/۰۹/۲۵

آشنايي با الگوي IOC يا Inversion of Control (واگذاري مسئوليت)


كلاس Kid را با تعريف زير در نظر بگيريد. هدف از آن نگهداري اطلاعات فرزندان يك شخص خاص مي‌باشد:

namespace IOCBeginnerGuide
{
class Kid
{
private int _age;
private string _name;

public Kid(int age, string name)
{
_age = age;
_name = name;
}

public override string ToString()
{
return "KID's Age: " + _age + ", Kid's Name: " + _name;
}
}
}

اكنون كلاس والد را با توجه به اينكه در حين ايجاد اين شيء، فرزندان او نيز بايد ايجاد شوند؛ در نظر بگيريد:
using System;

namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;
private Kid _obj;

public Parent(int personAge, string personName, int kidsAge, string kidsName)
{
_obj = new Kid(kidsAge, kidsName);
_age = personAge;
_name = personName;
}

public override string ToString()
{
Console.WriteLine(_obj);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}

و نهايتا مثالي از استفاده از آن توسط يك كلاينت:

using System;

namespace IOCBeginnerGuide
{
class Program
{
static void Main(string[] args)
{
Parent p = new Parent(35, "Dev", 6, "Len");
Console.WriteLine(p);

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

كه خروجي برنامه در اين حالت مساوي سطرهاي زير مي‌باشد:

KID's Age: 6, Kid's Name: Len
ParentAge: 35, ParentName: Dev

مثال فوق نمونه‌اي از الگوي طراحي تركيب يا composition مي‌باشد كه به آن Object Dependency يا Object Coupling نيز گفته مي‌شود. در اين حالت ايجاد شيء والد وابسته است به ايجاد شيء فرزند.

مشكلات اين روش:
1- با توجه به وابستگي شديد والد به فرزند، اگر نمونه سازي از شيء فرزند در سازنده‌ي كلاس والد با موفقيت روبرو نشود، ايجاد نمونه‌ي والد با شكست مواجه خواهد شد.
2- با از بين رفتن شيء والد، فرزندان او نيز از بين خواهند رفت.
3- هر تغييري در كلاس فرزند، نياز به تغيير در كلاس والد نيز دارد (اصطلاحا به آن Dangling Reference هم گفته مي‌شود. اين كلاس آويزان آن كلاس است!).

چگونه اين مشكلات را برطرف كنيم؟
بهتر است كار وهله سازي از كلاس Kid به يك شيء، متد يا حتي فريم ورك ديگري واگذار شود. به اين واگذاري مسئوليت، delegation و يا inversion of control - IOC نيز گفته مي‌شود.

بنابراين IOC مي‌گويد كه:
1- كلاس اصلي (يا همان Parent) نبايد به صورت مستقيم وابسته به كلاس‌هاي ديگر باشد.
2- رابطه‌ي بين كلاس‌ها بايد بر مبناي تعريف كلاس‌هاي abstract باشد (و يا استفاده از interface ها).

تزريق وابستگي يا Dependency injection
براي پياده سازي IOC از روش تزريق وابستگي يا dependency injection استفاده مي‌شود كه مي‌تواند بر اساس constructor injection ، setter injection و يا interface-based injection باشد و به صورت خلاصه پياده سازي يك شيء را از مرحله‌ي ساخت وهله‌اي از آن مجزا و ايزوله مي‌سازد.

مزاياي تزريق وابستگي‌ها:
1- گره خوردگي اشياء را حذف مي‌كند.
2- اشياء و برنامه را انعطاف پذيرتر كرده و اعمال تغييرات به آن‌ها ساده‌تر مي‌شود.

روش‌هاي متفاوت تزريق وابستگي به شرح زير هستند:

تزريق سازنده يا constructor injection :
در اين روش ارجاعي از شيء مورد استفاده، توسط سازنده‌ي كلاس استفاده كننده از آن دريافت مي‌شود. براي نمونه در مثال فوق از آنجائيكه كلاس والد به كلاس فرزندان وابسته است، يك ارجاع از شيء Kid به سازنده‌ي كلاس Parent بايد ارسال شود.
اكنون بر اين اساس تعاريف، كلاس‌هاي ما به شكل زير تغيير خواهند كرد:

//IBuisnessLogic.cs
namespace IOCBeginnerGuide
{
public interface IBuisnessLogic
{
}
}

//Kid.cs
namespace IOCBeginnerGuide
{
class Kid : IBuisnessLogic
{
private int _age;
private string _name;

public Kid(int age, string name)
{
_age = age;
_name = name;
}

public override string ToString()
{
return "KID's Age: " + _age + ", Kid's Name: " + _name;
}
}
}

//Parent.cs
using System;

namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;
private IBuisnessLogic _refKids;

public Parent(int personAge, string personName, IBuisnessLogic obj)
{
_age = personAge;
_name = personName;
_refKids = obj;
}

public override string ToString()
{
Console.WriteLine(_refKids);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}

//CIOC.cs
using System;

namespace IOCBeginnerGuide
{
class CIOC
{
Parent _p;

public void FactoryMethod()
{
IBuisnessLogic objKid = new Kid(12, "Ren");
_p = new Parent(42, "David", objKid);
}

public override string ToString()
{
Console.WriteLine(_p);
return "Displaying using Constructor Injection";
}
}
}

//Program.cs
using System;

namespace IOCBeginnerGuide
{
class Program
{
static void Main(string[] args)
{
CIOC obj = new CIOC();
obj.FactoryMethod();
Console.WriteLine(obj);

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

توضيحات:
ابتدا اينترفيس IBuisnessLogic ايجاد خواهد شد. تنها متدهاي اين اينترفيس در اختيار كلاس Parent قرار خواهند گرفت.
از آنجائيكه كلاس Kid توسط كلاس Parent استفاده خواهد شد، نياز است تا اين كلاس نيز اينترفيس IBuisnessLogic را پياده سازي كند.
اكنون سازنده‌ي كلاس Parent بجاي ارجاع مستقيم به شيء Kid ، از طريق اينترفيس IBuisnessLogic با آن ارتباط برقرار خواهد كرد.
در كلاس CIOC كار پياده سازي واگذاري مسئوليت وهله سازي از اشياء مورد نظر صورت گرفته است. اين وهله سازي در متدي به نام Factory انجام خواهد شد.
و در نهايت كلاينت ما تنها با كلاس IOC سركار دارد.

معايب اين روش:
- در اين حالت كلاس business logic، نمي‌تواند داراي سازنده‌ي پيش فرض باشد.
- هنگاميكه وهله‌اي از كلاس ايجاد شد ديگر نمي‌توان وابستگي‌ها را تغيير داد (چون از سازنده‌ي كلاس جهت ارسال مقادير مورد نظر استفاده شده است).

تزريق تنظيم كننده يا Setter injection
اين روش از خاصيت‌ها جهت تزريق وابستگي‌ها بجاي تزريق آن‌ها به سازنده‌ي كلاس استفاده مي‌كند. در اين حالت كلاس Parent مي‌تواند داراي سازنده‌ي پيش فرض نيز باشد.

مزاياي اين روش:
- از روش تزريق سازنده بسيار انعطاف پذيرتر است.
- در اين حالت بدون ايجاد وهله‌اي مي‌توان وابستگي اشياء را تغيير داد (چون سر و كار آن با سازنده‌ي كلاس نيست).
- بدون نياز به تغييري در سازنده‌ي يك كلاس مي‌توان وابستگي اشياء را تغيير داد.
- تنظيم كننده‌ها داراي نامي با معناتر و با مفهوم‌تر از سازنده‌ي يك كلاس مي‌باشند.

نحوه‌ي پياده سازي آن:
در اينجا مراحل ساخت Interface و همچنين كلاس Kid با روش قبل تفاوتي ندارند. همچنين كلاينت نهايي استفاده كننده از IOC نيز مانند روش قبل است. تنها كلاس‌هاي IOC و Parent بايد اندكي تغيير كنند:

//Parent.cs
using System;

namespace IOCBeginnerGuide
{
class Parent
{
private int _age;
private string _name;

public Parent(int personAge, string personName)
{
_age = personAge;
_name = personName;
}

public IBuisnessLogic RefKID {set; get;}

public override string ToString()
{
Console.WriteLine(RefKID);
return "ParentAge: " + _age + ", ParentName: " + _name;
}
}
}

//CIOC.cs
using System;

namespace IOCBeginnerGuide
{
class CIOC
{
Parent _p;

public void FactoryMethod()
{
IBuisnessLogic objKid = new Kid(12, "Ren");
_p = new Parent(42, "David");
_p.RefKID = objKid;
}

public override string ToString()
{
Console.WriteLine(_p);
return "Displaying using Setter Injection";
}
}
}

همانطور كه ملاحظه مي‌كنيد در اين روش يك خاصيت جديد به نام RefKID به كلاس Parent اضافه شده است كه از هر لحاظ نسبت به روش تزريق سازنده با مفهوم‌تر و خود توضيح دهنده‌تر است. سپس كلاس IOC جهت استفاده از اين خاصيت اندكي تغيير كرده است.

ماخذ