کنترل ساختن وابستگی‌ ها

اصل Inversion of Control می تواند به روشی دیگر برای کنترل کردن وظیفه ساختن اشیایی که به آنها وابستگی داریم نیز استفاده شود. در ابتدا بیایید در قالب یک مثال ساده ببینیم منظور از وابستگی چیست. لطفاً کدی که در ادامه آمده است را نگاه کنید.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 متد SomeMethod از کلاس B را برای انجام دادن وظیفه خود که تحت عنوان Task1 تعریف شده است صدا می ‌زند. در واقع کلاس A نمی تواند این وظیفه را بدون استفاده از کلاس B و متد مذکور انجام دهد. به همین دلیل می توانیم بگوییم که کلاس A وابسته به کلاس B است یا اینکه کلاس B یک وابستگی یا همان Dependency برای کلاس A است. در طراحی شی گرا کلاس ها اغلب نیاز به تعامل با همدیگر دارند تا بتوانند وظایف برنامه یا Functionality های آن را انجام دهند.همانطور که در مثال بالا دیدید کلاس A برای انجام کاری به یک شی از کلاس B نیازمند است. در مثال بالا کلاس A یک شی از کلاس B را می‌سازد و حتی چرخه حیات (Lifetime) آن را نیز مدیریت می‌کند. ساده بگوییم کنترل ساخت و چرخه حیات کلاس B به عهده ی کلاس A است. بنابراین یک وابستگی بین کلاس A و کلاس B وجود دارد. بر اساس اصل Inversion of Control ما باید این کنترل را معکوس کنیم. به عبارتی وظیفه ساختن یک شی از کلاس B را نه به کلاس A بلکه به یک کلاس دیگر بدهیم. همانطور که در کد زیر مشاهده می کنید وظیفه ساختن این وابستگی که همان کلاس B است را از کلاس A گرفته و آن را به یک کلاس دیگر تحت عنوان Factory داده ایم.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 نیست بلکه کلاس A از یک کلاس دیگر به نام Factory برای به دست آوردن یک شی از کلاس B استفاده می کنند.حال بیایید همین موضوع یعنی کنترل ساختن Dependency ها را در یک مثال کاربردی تر ببینیم. در طراحی شی گرا کلاس ها باید به صورت از Loosely Coupled طراحی شوند. Loosely Coupled بودن به این معنی است که تغییرات در یک کلاس نباید باعث ایجاد تغییراتی در کلاس ‌های دیگر شود. این باعث می شود که برنامه به طور کلی قابلیت نگهداری (Maintainability) و قابلیت گسترش (Extensibility) بالاتری داشته باشد. بیایید با نگاه کردن به تصویر زیر که یک معماری N-Tier معمول را نشان می دهد این موضوع را بررسی کنیم.?در یک معماری N-Tier معمول واسط کاربری برنامه (UI) از لایه سرویس (Service Layer) برای بازیابی و ذخیره کردن داده ها استفاده می‌کند. لایه ی سرویس (Service Layer) از کلاس‌ Business Logic برای اعمال کردن Business Rule ها بر روی داده استفاده می‌کند. کلاس Business Logic به کلاس Data Acces وابستگی دارد و وظیفه ی کلاس Data Access بازیابی یا ذخیره کردن داده ها بر روی دیتابیس است. این یک معماری N-Tier ساده است. در ادامه بیاید بر روی کلاس های Data Access و Business Logic تمرکز کرده تا بتوانیم مفهوم Inversion of Control را در این مثال متوجه شویم.کدی که در ادامه می بینید دو کلاس ساده ی Business Logic و Data Access را نشان می دهد که برای Entity ی به نام Customer یا همان مشتری پیاده‌ سازی شده اند.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 &amp;quot;Dummy Customer Name&amp;quot;; // get it from DB in real app}}همانطور که از کد بالا متوجه می‌شوید کلاس CustomerBusinessLogic به کلاس DataAcces وابستگی دارد. به عبارت دیگر این کلاس برای خواندن اطلاعات مربوط به مشتری یک شی از کلاس DataAccess را در درون خود ایجاد می‌کند. حال بیایید در رابطه با مشکلات این نوع طراحی صحبت کنیم.در مثال بالا دو کلاس CustomerBusinessLogic و DataAccess با هم Tight Coupling (در هم تنیدگی سخت) دارند چرا که CustomerBusinessLogic شامل یک رفرنس به کلاس DataAccess است. به عبارت دیگر یک شی از کلاس DataAccess در درون CustomerBusinessLogic تعریف شده و چرخه حیات (Lifetime) آن توسط این کلاس مدیریت می شود.مشکلات مربوط به این نوع طراحی از قرار زیر می باشند:کلاسهای CustomerBusinessLogic و DataAccess با هم Tight Coupling دارند. بنابراین تغییرات در کلاس DataAccess منجر به تغییرات در کلاس CustomerBusinessLogic می شود. برای مثال اگر ما یک متد در DataAccess را حذف و یا اضافه کنیم یا حتی تغییر نام دهیم نیازمند تغییر دادن کد های CustomerBusinessLogic که از این متد استفاده می کنند نیز هستیم.فرض کنید که در آینده امکان دارد داده های مربوط به CustomerBusinessLogic از دیتابیس ها یا وب سرویس های مختلفی وارد برنامه شوند. برای مدیریت کردن چنین شرایطی نیاز به ساخت کلاس های DataAccess دیگر داریم تا بتوانیم داده ها را از منابع مختلف دریافت کنیم. این تغییرات نیز منجر به ایجاد تغییراتی در CustomerBusinessLogic نیز می شوند.همانطور که می دانید کلاس CustomerBusinessLogic یک شی از کلاس DataAccess را با استفاده از کلمه کلیدی new در درون خود ایجاد می‌کند. ممکن است کلاس های متفاوتی در برنامه ما وجود داشته باشند که از کلاس DataAccess استفاده کنند و از آن یک شی بسازند. بنابراین اگر نام کلاس DataAccess در آینده تغییر کند باید تمامی مکان هایی که یک شی از این کلاس ساخته شده است را پیدا کرده و این تغییرات را در آنجا اعمال کنیم.از آنجایی که کلاس CustomerBusinessLogic یک شی از کلاس DataAccess (دقت کنید که DataAccess یک کلاس Concrete است نه یک کلاس Abstract یا اینترفیس) را در درون خود ایجاد کرده است نمی‌تواند به طور مستقل در Test Driven Development استفاده شود. به عبارت دیگر CustomerBusinessLogic نمی تواند با کلاس‌های Mock جایگزین شود. اگر در این رابطه اطلاعات بیشتری می خواهید توصیه می‌کنیم حتماً از بسته های آموزش وب سایت پرووید در رابطه با Test Driven Development و Unit Testing استفاده کنید.رای حل تمامی مشکلاتی که از آنها یاد شد و رسیدن به یک طراحی Loosely Coupled ما می‌توانیم از اصول Inversion of Control و Dependency Inversion استفاده کنیم. دقت کنید که Inversion of Control یک Principle است و نه یک Design Pattern. این که Inversion of Control یک Principle است به این معنی است که توصیه های سطح بالایی (High Level Guideline) در رابطه با طراحی را ارائه می ‌کند و ربطی به جزئیات پیاده‌ سازی (Implemention Detail) ندارد. شما می توانید اصل Inversion of Control را به روش های مختلفی در برنامه خود پیاده سازی کنید.تصویری که در ادامه می بینید یکی از روش ‌های پیاده‌سازی اصل Inversion of Control است.بیاید از الگوی طراحی Factory برای پیاده ‌سازی اصل Inversion of Control استفاده کنیم. این موضوع اتفاقی است که در تصویر بالا نیز نشان داده شده است. این اولین قدم ما در رسیدن به کلاس‌ های Loosely Coupled می باشد. برای انجام این کار ابتدا یک کلاس ساده با نام DataAccessFactory را ایجاد می کنیم. همانطور که در تصویر زیر می بینید در درون این کلاس متد GetDataAccessObj یک شی از کلاس DataAccess را بر میگرداند.public class DataAccessFactory{public static DataAccess GetDataAccessObj(){return new DataAccess();}}سپس در درون کلاس CustomerBusinessLogic از این کلاس استفاده کرده و توسط آن یک شی از کلاس DataAccess را به دست می آوریم. کدی که در ادامه می بینید این موضوع را نشان می دهد.public class CustomerBusinessLogic{public CustomerBusinessLogic(){}public string GetCustomerName(int id){DataAccess _dataAccess = DataAccessFactory.GetDataAccessObj();return _dataAccess.GetCustomerName(id);}}همانطور که در کد بالا مشهود است کلاس CustomerBusinessLogic از متد GetDataAccessObj از کلاس DataAccessFactory برای ساختن یک شی جدید از کلاس DataAccess استفاده می شود. این روش را مقایسه کنید با روش قبلی که از کلید واژه new برای ساخت یک شی از کلاس DataAccess در درون کلاس CustomerBusinessLogic استفاده می شد. به طور خلاصه ما مسئولیت ساختن و کنترل کردن یک شی جدید از کلاس DataAccess که Dependency کلاس CustomerBusinessLogic است را از آن گرفته و به کلاس DataAccessFactory تحویل داده ایم.این روش ساده ترین پیاده سازی اصل Inversion of Control و اولین قدم ما در رسیدن به یک طراحی کاملا Loose Coupled است. اما همانطور که در قسمت های قبلی این آموزش ذکر شد صرفا با استفاده از اصل Inversion of Control نمی توان به یک طراحی کاملا Loose Couples رسید. در کنار اصل Inversion of Control ما نیاز به استفاده کردن از اصل Dependency Inversion الگوی طراحی Strategy و Dependency Injection داریم.پرووید: مرکز آموزش تخصصی برنامه نویسی و توسعه نرم افزار