انتزاع یا Abstraction و تلفیق یا Encapsulation دو تا از مهمترین اصول در برنامه نویسی شی گرا هستند. تعریف های زیادی توسط افراد مختلف برای این دو اصل در نظر گرفته شده است. دو مفهوم Abstract و Concrete در طراحی شی گرا اغلب متضاد هم هستند. در زبان انگلیسی چیزی که Abstract باشد غیر ملموس یا Non-concrete است. از نقطه نظر برنامه نویسی دو کلاس CustomerBusinessLogic و DataAccess کلاس های Concrete هستند. به این معنا که ما می توانیم از آنها اشیایی بسازیم. بنابراین، Abstraction در زبان های برنامه نویسی به معنی ساختن یک Interface یا کلاس Abstract ایست که Non-concrete باشد یا به عبارتی نتوانیم از آن یک شی بسازیم.
همانطور که ممکن است بدانید در زبان های برنامه نویسی شی گرا از قبیل سی شارپ نمیتوان یک شی از یک Interface یا یک کلاس Abstract ساخت. اگر بخواهیم این مفاهیم را در کنار قواعد مربوط به Dependency Inversion بگوییم کلاس CustomerBusinessLogic که یک ماژول سطح بالا است نباید به کلاس DataAccess که یک کلاس Concrete و البته ماژول سطح پایین است وابسته باشد. بلکه هر دوی این کلاس ها باید وابسته به Abstraction باشند یا به عبارتی هر دوی این کلاس ها باید وابسته به Interface یا کلاس Abstract باشند.
سوالی که ممکن است در این قسمت مطرح شود این است که در Interface یا کلاس Abstract ی خواهیم ساخت چه چیزی را باید قرار دهیم؟ همان طور که ممکن است یادتان باشد دیدید که کلاس CustomerBusinessLogic از مدتی به نام GetCustomerName که در کلاس DataAccess تعریف شده بود استفاده میکرد. دقت کنید که در دنیای واقعی ممکن است متد های بیشتری در رابطه با کار کردن با داده های Customer در کلاس DataAccess تعریف شود. اما برای مثال ما همین یک متد کفایت می کند. در واقع ما باید همین متد را در Interface مان قرار دهیم.
همان طور که در کد زیر میبینید Interface ی با نام ICustomerDataAcces تعریف شده است و در آن متد GetCustomerName قرار داده شده است که یک پارامتر ورودی از نوع Interface دریافت می کند.
public
interface
ICustomerDataAccess
{
string
GetCustomerName(int
id);
}
در ادامه کلاس CustomerDataAcces را داریم که اینترفیس ICustomerDataAcces را پیاده سازی می کند.
public
class
CustomerDataAccess: ICustomerDataAccess
{
public
CustomerDataAccess()
{
}
public
string
GetCustomerName(int
id) {
return
"Dummy Customer Name";
}
}
لطفا دقت کنید که ما نام کلاس را از DataAccess به CustomerDataAcces تغییر داده ایم. این موضوع می تواند به قابلیت Readability (قابلیت خوانایی) برنامه کمک کند. پس از آن ما نیاز داریم کلاس DataAccessFactory را طوری تغییر دهیم که به جای برگرداندن یک شی از کلاس DataAccess یک شی از اینترفیس ICustomerDataAcces را برگرداند.
public
class
DataAccessFactory
{
public
static
ICustomerDataAccess GetCustomerDataAccessObj()
{
return
new
CustomerDataAccess();
}
}
در ادامه کلاس CustomerBusinessLogic را می بینیم که به جای استفاده کردن از کلاس DataAccess از اینترفیس ICustomerDataAcces استفاده می کند.
public
class
CustomerBusinessLogic
{
ICustomerDataAccess _custDataAccess;
public
CustomerBusinessLogic()
{
_custDataAccess = DataAccessFactory.GetCustomerDataAccessObj();
}
public
string
GetCustomerName(int
id)
{
return
_custDataAccess.GetCustomerName(id);
}
}
تا اینجای کار ما توانسته ایم که اصل Dependency Inversion را در مثال خود پیاده سازی کنیم. هم اکنون ماژول سطح بالای ما یعنی کلاس CustomerBusinessLogic به ماژول سطح پایین یعنی CustomerDataAccess وابسته نیست. بلکه هر دوی آنها به یک Abstraction که در قالب یک اینترفیس با نام ICustomerDataAcces پیاده سازی شده است وابسته هستند.
علاوه بر این اینترفیس ICustomerDataAcces که در نقش یک Abstraction قرار گرفته است بند دوم از تعریف اصل Dependency Inversion را بر قرار می کند. به عبارت دیگر، Abstraction یعنی همان اینترفیس ICustomerDataAcces به جزئیات یعنی کلاس CustomerDataAcces وابسته نیست. بلکه جزئیات یعنی کلاس CustomerDataAcces به Abstraction یعنی اینترفیس ICustomerDataAcces وابسته است. منظور از جزئیات در این قسمت جزئیات پیاده سازی یا همان Implementation Details می باشد. در ادامه کد کامل مربوط به مثال اصل Dependency Inversion را می بینید.
public
interface
ICustomerDataAccess
{
string
GetCustomerName(int
id);
}
public
class
CustomerDataAccess: ICustomerDataAccess
{
public
CustomerDataAccess() {
}
public
string
GetCustomerName(int
id) {
return
"Dummy Customer Name";
}
}
public
class
DataAccessFactory
{
public
static
ICustomerDataAccess GetCustomerDataAccessObj()
{
return
new
CustomerDataAccess();
}
}
public
class
CustomerBusinessLogic
{
ICustomerDataAccess _custDataAccess;
public
CustomerBusinessLogic()
{
_custDataAccess = DataAccessFactory.GetCustomerDataAccessObj();
}
public
string
GetCustomerName(int
id)
{
return
_custDataAccess.GetCustomerName(id);
}
}
ممکن است فهمیده باشید که مهمترین مزیت پیاده سازی اصل Dependency Inversion در مثال بالا این است که کلاس های CustomerBusinessLogic و CustomerDataAcces به صورت Loosely Coupled طراحی شده اند. چرا که CustomerBusinessLogic وابسته به کلاس DataAccess که یک کلاس Concrete است نمی باشد. بلکه وابستگی کلاس CustomerBusinessLogic این بار به Abstraction یا Interface ی به نام ICustomerDataAcces است.
نتیجه ی این کار این است که ما می توانیم به سادگی از یک کلاس دیگر که اینترفیس ICustomerDataAcces پیاده سازی می کند و برای متد GetCustomerName آن یک بدنه پیاده سازی می کند را مورد استفاده قرار دهیم.
اگر خاطرتان باشد در ابتدای این آموزش عرض کردیم که یکی از مشکلات کلاس هایی که Tight Coupling دارند این است که اگر بخواهیم اطلاعات مربوط به یک مشتری را از یک منبع داده ای دیگر مثل یک وب سرویس بخوانیم با مشکلاتی روبرو خواهیم شد. اما با این روش خیلی راحت میتوانیم هر DataAccess دیگری را به شرط اینکه اینترفیس ICustomerDataAcces را پیاده سازی کند در درون CustomerBusinessLogic مورد استفاده قرار دهیم. با همه تلاش هایی که تا اینجا انجام داده ایم هنوز کلاس های ما به صورت کاملا Loosely Coupled نیستند چرا که کلاس CustomerBusinessLogic نیازمند استفاده کردن از کلاس Factory برای به دست آوردن یک شی از اینترفیس ICustomerDataAcces است. در ادامه ما از تکنیک Dependency Injection یا همان تزریق وابستگی که یک الگوی طراحی اسن استفاده خواهیم کرد.
منبع: وبسایت پرووید