مشخصات مقاله
-
853
-
0.0
-
2888
-
0
-
0
استفاده از وارونگی کنترل
وارونگی کنترل
در این فصل، در رابطه با IoC و چگونگی پیاده سازی آن خواهیم آموخت. همانطور که از شکل زیر مشخص است، این بخش اولین قدم به سمت رسیدن به طراحی با همراهی آزادانه است.
واورنگی کنترل (IoC) یک اصل طراحی است (هرچند که برخی از افراد آن را یک الگو می دانند). همانطور که از اسم IoC پیداست، این اصل در وارونه کردن انواع مختلفی از کنترل های موجود در طراحی شیء گرا کاربرد دارد تا بتوان به همراهی آزادانه رسید. در اینجا، کنترل به تمامی مسئولیت های اضافی یک کلاس به غیر از مسئولیت اصلی آن گفته می شود، مانند کنترل جریان برنامه، کنترل جریان ایجاد شیء، یا ایجاد شیء وابسته و binding.
IoC حول محور وارونه سازی کنترل می گردد. به بیان عامیانه، فرض کنید که سوار بر خودروی خود هستید و در مسیر محل کار خود در حال رانندگی هستید، این یعنی شما در حال کنترل خودروی خود هستید. اصل IoC وارونه کردن کنترل را پیشنهاد می دهد. این یعنی شما به جای آنکه خودتان رانندگی کنید، یک تاکسی بگیرید و بگذارید فرد دیگری رانندگی کند. در این صورت است که کنترل از شما به راننده ی تاکسی وارونه می شود. شما دیگر مجبور نیستید خودتان رانندگی کنید و برای آنکه بتوانید بر روی کار اصلی خود تمرکز کنید، این کار را به راننده ی تاکسی محول می کنید.
پس از آنکه با کمک این اصل به طراحی کلاس های دارای همراهی آزادانه رسیدیم، این کلاس ها قابل آزمایش، قابل نگهداری و توسعه پذیر می شوند.
حالا بیایید بفهمیم که چگونه IoC انواع مختلفی از کنترل را وارونه می کند.
کنترل جریان برنامه
در یک برنامه ی معمولی در C# اجرای برنامه از تابع Main() آغاز می شود. این تابع جریان برنامه و یا به بیان دیگر رشته ای از تعامل کاربر را کنترل می کند. برنامه ی کنسول ساده ی زیر را در نظر بگیرید.
مثال : جریان برنامه
namespace FlowControlDemo
{
class Program
{
static void Main(string[] args)
{
bool continueExecution = true;
do
{
Console.Write("Enter First Name:");
var firstName = Console.ReadLine();
Console.Write("Enter Last Name:");
var lastName = Console.ReadLine();
Console.Write("Do you want to save it? Y/N: ");
var wantToSave = Console.ReadLine();
if (wantToSave.ToUpper() == "Y")
SaveToDB(firstName, lastName);
Console.Write("Do you want to exit? Y/N: ");
var wantToExit = Console.ReadLine();
if (wantToExit.ToUpper() == "Y")
continueExecution = false;
}while (continueExecution);
}
private static void SaveToDB(string firstName, string lastName)
{
//save firstName and lastName to the database here..
}
}
}
در مثال بالا، تابع Main() مربوط به کلاس برنامه جریان برنامه ای را کنترل می کند. این تابع ورودی نام و نام خانوادگی را دریافت می کند. همچنین داده ها را ذخیره می کند و براساس ورودی کاربر از کنسول خارج می شود و یا به راه خود ادامه می دهد. به این صورت جریان کنترل از طریق تابع Main() انجام می شود.
با ایجاد برنامه ای مبتنی بر GUI مانند برنامه ی ویندوزی زیر که در آن فریمورک با استفاده از رویدادها جریان یک برنامه را مدیریت می کند، می توان IoC را در برنامه ی بالا به کار برد.
این برنامه، مثال ساده ای از پیاده سازی IoC در جریان یک برنامه است.
کنترل ایجاد شیء وابسته
IoC را مانند روش ایجاد اشیاء کلاس وابسته نیز می توان به کار برد. اول از همه بیایید ببینیم منظور ما از وابستگی چیست.
مثال زیر را در نظر بگیرید.
public class A
{
B b;
public A()
{
b = new B();
}
public void Task1() {
// do something here..
b.SomeMethod();
// do something here..
}
}
public class B {
public void SomeMethod() {
//doing something..
}
}
در این مثال، کلاس A برای کامل کردن task1 خودش b.SomeMethod() را فراخوانی می کند. کلاس A بدون کلاس B نمی تواند وظیفه ی خود را انجام دهد. بنابراین، می توان گفت «کلاس A به کلاس B وابسته است» و یا «کلاس B وابستگی کلاس A است».
در طراحی شیء گرا، کلاس ها باید با یکدیگر تعامل کنند تا بتوانند یک یا چند کار یک برنامه را مانند کلاس های A و B در بالا، به سرانجام برسانند. کلاس A عمر شی از کلاس B را ایجاد و مدیریت می کند. این کلاس در اصل ایجاد و عمر اشیاء کلاس وابستگی را کنترل می کند.
اصل IoC وارونه سازی کنترل را پیشنهاد می دهد. این به معنی جدا کردن بخش های کنترلی و انتقال آن ها به کلاسی دیگر است. به بیان دیگر، این به معنی وارونه کردن کنترل ایجاد وابستگی از کلاس B به کلاس دیگر است. مانند زیر:
public class A
{
B b;
public A()
{
b = Factory.GetObjectOfB ();
}
public void Task1() {
// do something here..
b.SomeMethod();
// do something here..
}
}
public class Factory
{
public static B GetObjectOfB()
{
return new B();
}
}
همانطور که در بالا مشاهده می کنید، کلاس A برای دریافت شی از کلاس B از کلاس Factory استفاده می کند. به این صورت ما ایجاد شیء وابسته را از کلاس A به کلاس Factory وارونه کرده ایم. از این به بعد کلاس A دیگر شیئی از کلاس B را ایجاد نمی کند، بلکه برای دریافت این شیء از کلاس Factory استفاده می کند.
بیایید این موضوع را به کمک مثال کاربردی تری بررسی کنیم.
در طراحی شیء گرا کلاس ها باید به صورت همراهی آزادانه طراحی شوند. همراهی آزادانه یعنی تغییرات ایجاد شده در یک کلاس نباید کلاس دیگر را مجبور به تغییر کند. به این صورت کل برنامه می تواند قابل نگهداری و توسعه پذیر شود. بیایید این موضوع را به کمک معماری رایج n لایه ای (مانند زیر) بهتر درک کنیم.
در این معماری، رابط کاربری برای بازیابی یا ذخیره سازی داده ها از لایه ی سرویس استفاده می کند.
این لایه برای به کار بستن مقررات در داده ها از کلاس BusinessLogic استفاده می کند. این کلاس به کلاس DataAccess وابسته است که داده ها را در دیتابیس زیرساختی ذخیره سازی یا بازیابی می کند. این مثال ساده از طراحی معماری n لایه ای بود. حالا بیایید برای شناخت بهتر IoC بر روی BusinessLogic و DataAccess متمرکز شویم.
مثال زیر مربوط به کلاس های DataAccess و BusinessLogic برای یک مشتری است.
public class CustomerBusinessLogic
{
DataAccess _dataAccess;
public CustomerBusinessLogic()
{
_dataAccess = new DataAccess();
}
public string GetCustomerName(int id)
{
return _dataAccess.GetCustomerName(id);
}
}
public class DataAccess
{
public DataAccess()
{
}
public string GetCustomerName(int id) {
return "Dummy Customer Name"; // get it from DB in real app
}
}
همانطور که در این مثال مشاهده می کنید، کلاس CustomerBusinessLogic به کلاس DataAccess وابسته است. این کلاس شیئی از کلاس DataAccess را ایجاد می کند تا بتواند اطلاعات مشتری را دریافت کند.
حالا بیایید مشکل کلاس های بالا را پیدا کنیم.
در مثال بالا، CustomerBusinessLogic و DataAccess دارای همراهی آزادانه نیستند. زیرا کلاس CustomerBusinessLogic شامل مرجع کلاس کانکریت DataAccess است. این کلاس همچنین شیئی از کلاس DataAccess را ایجاد می کند و عمر یک شیء را مدیریت می کند.
مشکلات موجود در نمونه کلاس های بالا:
- کلاس های CustomerBusinessLogic و DataAccess دارای همراهی آزادانه نیستند. بنابراین، هر تغییری که در کلاس DataAccess ایجاد شود منجر به اعمال تغییر در کلاس CustomerBusinessLogic می شود. مثلا اگر ما هر متدی را به کلاس DataAccess اضافه و یا از آن حذف کنیم، و یا اینکه اسم متدی را تغییر دهیم، در این صورت نیاز است که بر همین اساس کلاس CustomerBusinessLogic را نیز تغییر دهیم.
- فرض کنید که اطلاعات مشتری از دیتابیس یا خدمات شبکه ای متفاوتی بیایند. در آینده ممکن است نیاز شود کلاس های مختلفی را ایجاد کنیم. همین امر باعث تغییر کلاس CustomerBusinessLogic می شود.
- کلاس CustomerBusinessLogic با استفاده از عبارت کلیدی new شیئی از کلاس DataAccess را ایجاد می کند. ممکن است کلاس های متعددی وجود داشته باشند که از کلاس DataAccess استفاده کنند و شیء آن را ایجاد کنند. بنابراین اگر شما اسم کلاس را تغییر دهید، در این صورت باید تمامی بخش های سورس کدتان که مربوط به ایجاد اشیاء DataAccess است را پیدا کنید و کل کد را تغییر دهید. این کدی تکراری برای ایجاد شیئی از همان کلاس و حفظ وابستگی های آن است.
- با توجه به اینکه کلاس CustomerBusinessLogic شیئی از کلاس کانکریت DataAccess را ایجاد می کند، این کلاس را نمی توان به صورت مستقل آزمایش کرد (TDD). جای کلاس DataAccess را نمی توان با کلاس ساختگی عوض کرد.
بنابراین، برای حل مشکلات بالا و برای آنکه به یک طراحی دارای همراهی آزادانه برسیم، می توانیم از اصول IoC و DIP به صورت همزمان استفاده کنیم. به خاطر داشته باشید که IoC یک اصل است نه الگو. IoC تنها راهنمایی های طراحی سطح بالا را ارائه می کند اما کاری به جزئیات پیاده سازی ندارد. هر طور که مایل باشید می توانید اصل IoC را پیاده سازی کنید.
الگوی زیر یکی از روش های پیاده سازی اصل IoC را نشان می دهد.
بیایید به عنوان اولین قدم به سمت کلاس های دارای همراهی آزادانه، جهت پیاده سازی IoC از الگوی Factory استفاده کنیم.
ابتدا، یک کلاس ساده ی Factory را ایجاد کنید. به گونه ای که این کلاس مانند زیر شی از کلاس DataAccess را برگشت دهد.
مثال : DataAccess Factory
public class DataAccessFactory
{
public static DataAccess GetDataAccessObj()
{
return new DataAccess();
}
}
حالا، کلاس از DataAccessFactory در کلاس CustomerBusinessLogic استفاده کنید تا بتوانید شی از کلاس DataAccess را دریافت کنید.
مثال : استفاده از کلاس Factory برای بازیابی شیء
public class CustomerBusinessLogic
{
public CustomerBusinessLogic()
{
}
public string GetCustomerName(int id)
{
DataAccess _dataAccess = DataAccessFactory.GetDataAccessObj();
return _dataAccess.GetCustomerName(id);
}
}
همانطور که می بینید، کلاس CustomerBusinessLogic برای دریافت شیئی از کلاس DataAccess از متد DataAccessFactory.GetCustomerDataAccessObj() استفاده می کند، به جای آنکه این کار را از طریق عبارت کلیدی new انجام دهد. به این صورت کنترل ایجاد شیئی از کلاس وابسته را از کلاس CustomerBusinessLogic به کلاس DataAccessFactory وارونه کردیم.
این روش پیاده سازی آسانی از IoC و اولین قدم به سمت دستیابی به طراحی دارای همراهی آزادانه است. همانطور که در فصل قبل نیز اشاره کردیم، تنها با استفاده از IoC نمی توان به صورت کامل به همراهی آزادانه دست یافت، بلکه برای انجام این کار به DIP، الگوی استراتژی و DI (تزریق وابستگی) نیز نیاز داریم.
بیایید برای شناخت DIP و نقش آن در دست یابی به طراحی دارای همراهی آزادانه، به مرحله ی دوم در فصل بعد برویم.
برای مطالعه سرفصل اصول و الگوهای طراحی شی گرا - Object Oriented Design Principles & Patterns کلیک نمایید .