مقاله: آموزش برنامه نویسی شئ گرا در زبان سی شارپ - قسمت بیست و سوم :: آشنایی با مفاهیم Inversion of Control و Dependency Injection
در قسمت قبلی سری آموزشی با مفهوم interface ها آشنا شدیم. interface ها
نقش بسیار موثری در روند نوشتن یک برنامه بازی می کنند و در صورتی که یک
برنامه نویس با نحوه استفاده صحیح از interface ها آشنا باشه، توانایی
ایجاد کدهایی ساختاریافته و قابل گستری و نگهداری رو داره. در این قسمت از
سری آموزشی زبان سی
شارپ و برنامه نویسی شئ گرا، با دو مفهوم بسیار مهم در برنامه نویسی آشنا
خواهیم شد، IoC که مخفف Inversion of Control و DI که مخفف Dependency
Injection هست.همه ما در طول زندگی با وسایل زیادی سر و کار داریم، از
وسایل اولیه زندگی مانند ماشین، یخچال، تلویزیون و ... تا وسایل که هر کس
بر اساس نیاز کاری خودش با اونها سر و کار داره، مانند کامپیوتر یا لپ تاپ و
گوشی. برای مثال، گوشی هوشمند خود را فرض کنید، این گوشی از قطعات زیادی
تشکیل شده، صفحه نمایش، پردازنده، حافظه رم، باتری و کلی قطعات دیگه. حالا
اتفاقی پیش میاد و خدای نکرده گوشی شما از دستتون میافته و صفحه نمایش
گوشیتون آسیب میبینه.
کاری که می کنید باید گوشی رو به یک نمایندگی برده و صفحه نمایش رو تغییر
بدید. حالا فرض کنید که گوشی شما جوری طراحی شده باشه که با آسیب دیدن صفحه
نمایش نیاز باشه تا یک گوشی جدید تهیه کنید!!!! یا برای کارتون یک لپ تاپ
تهیه کردید. بعد از مدتی نیاز دارید تا حافظه رم لپ تاپ رو افزایش بدید. در
این حالت شما لپ تاپ رو پیش نمایندگی یا یک کارشناس در این زمینه می برید و
حافظه رم لپ تاپ شما افزایش داده میشه. حال فرض کنید که لپ تاپ شما همچین
قابلیتی نداشته باشه و شما نیاز باشه برای تغییر یا ارتقا حافظه یک لپ تاپ
جدید خریداری کنید! برای حل این مشکل لوازم الکتریکی از قطعات مختلفی تشکیل
شدند که قابلیت تغییر یا تعویض دارند. به این قابلیت طراحی ماژولار گفته
میشه. در پیاده سازی سیستم های نرم افزاری نیز شما نیز به عنوان برنامه
نویس باید با همچین دیدی نسبت به پیاده سازی نرم افزار اقدام کنید. ما در
اینجا در مورد تغییر یکی از قسمت های نرم افزار صحبت خواهیم کرد که ارتباط
مستقیمی به interface ها و IoC و DI دارد.برای آشنایی بیشتر با این مفاهیم،
با یک مثال جلو میرویم. فرض کنید سیستمی پیاده سازی کردید که اعضاء می
توانند در این سامانه اقدام به ثبت نام کنند. برای عملیات های مرتبط با
مدیریت اعضاء کلاسی با نام Members می نویسیم:
public class Members { public void Register(string firstName, string lastName) { // add member to database } }
همانطور که مشاهده می کنید این کلاس یک متد با نام Register دارد که عملیات ثبت نام اعضاء را انجام می دهد (تنها متد تعریف شده و کدی برای ثبت نام نوشته نشده است). پس از مدتی، فردی که درخواست پیاده سازی نرم افزار را از شما داشته، می خواهد زمانی که یک عضو جدید به سامانه اضافه شد، یک ایمیل برای شخص ارسال شود. کد کلاس به صورت زیر تغییر می کند:
public class Members { public void Register(string firstName, string lastName) { // add member to database // send email to name@host.com } }
تا اینجا مشکلی نیست، بعد از مدتی مجدداً شخص ذکر شده از شما می خواهد به جای ارسال ایمیل، یک پیامک برای او ارسال شود. شما مجدداً باید کد داخل متد Register را تغییر داده و عملیات ارسال پیامک را اضافه کنید:
public class Members { public void Register(string firstName, string lastName) { // add member to database // send sms to 0912******* } }
با هر درخواست، ما دائماً در حال تغییر کدهای نوشته شده داخل کلاس Members هستیم. اگر کمی اصولی کار کنیم، کلاسی با نام SmsManager ایجاد می کنیم و از آن کلاس داخل کلاس Members استفاده می کنیم:
public class SmsManager { public void Send(string message) { // send sms to 0912******* } } public class Members { public void Register(string firstName, string lastName) { // add member to database var sms = new SmsManager(); sms.Send("New user registered!"); } }
حالا فرض کنید که مجدد، شخص ذکر شده درخواست جایگزینی ارسال ایمیل به جای پیامک را به ما می دهد، ما کلاسی با نام EmailManager تعریف کرده و از آن استفاده می کنیم:
public class EmailManager { public void Send(string message) { // send message to name@host.com } } public class SmsManager { public void Send(string message) { // send sms to 0912******* } } public class Members { public void Register(string firstName, string lastName) { // add member to database var email = new EmailManager(); email.Send("New user registered!"); } }
با تعریف کلاس های EmailManager و SmsManager، حجم تغییرات کلاس Members خیلی کم شد. اما می توان این تغییرات را خیلی کمتر کرد. در اینجا می خواهیم یکی از کاربردهای بسیار مهم interface ها، یعنی IoC یا Inversion of Control را بررسی کنیم. در ابتدا ما یک interface با نام INotifySender به صورت زیر ایجاد می کنیم:
public interface INotifySender { void Send(string message); }
اگر دقت کنید، متد signature متد Send در کلاس های EmailManager و SmsManager مشترک است. پس این دو کلاس قابلیت پیاده سازی INotifySender را دارند. کلاس های ذکر شده را به صورت زیر تغییر می دهیم:
public class EmailManager : INotifySender { public void Send(string message) { // send message to name@host.com } } public class SmsManager : INotifySender { public void Send(string message) { // send sms to 0912******* } }
در قدم بعدی باید کلاس Members را جوری تغییر دهیم تا وابستگی متد Register به یک کلاس خاص از بین برود، یعنی به کلاس EmailManager یا SmsManager وابسته نباشد. اینکار را می توان با استفاده از INotifySender انجام داد. کد کلاس Members را به صورت زیر تغییر می دهیم:
public class Members { private readonly INotifySender sender; public Members() { sender = new EmailManager(); } public void Register(string firstName, string lastName) { // add member to database sender.Send("New member registered!"); } }
تغییرات کد بالا را با هم بررسی می کنیم، فیلدی تعریف کردیم از نوع INotifySender که داخل متد Register از این فیلد برای ارسال پیام استفاده می کنیم. این فیلد از داخل سازنده کلاس Members مقدار دهی می شود. در کد بالا ما شئ ای از نوع EmailManager که INotifySender را پیاده سازی کرده است ایجاد کرده و داخل فیلد sender میریزیم. حال فرض کنید که بخواهیم عملیات را از ایمیل به پیامک تغییر دهیم، برای اینکار تنها سازنده را به صورت زیر تغییر می دهیم:
public class Members { private readonly INotifySender sender; public Members() { sender = new SmsManager(); } public void Register(string firstName, string lastName) { // add member to database sender.Send("New member registered!"); } }
به عملیات بالا، Inversion of Control گفته می شود. ما در حقیقت وابستگی متد Register را به یک کلاس خاص از بین بردیم. به این نوع پیاده سازی tightly coupled گفته می شود. اما باز یک مشکل وجود دارد، ما هنوز هم در حال تغییر کلاس Members هستیم. در قدم بعد باید وابستگی کلاس Members را به کلاس های EmailManager و SmsManager به طور کامل از بین ببریم. برای اینکار، ما به جای ساختن شئ داخل سازنده، آن را به عنوان یک پارامتر به سازنده کلاس Members ارسال می کنیم:
public class Members { private readonly INotifySender sender; public Members(INotifySender sender) { this.sender = sender; } public void Register(string firstName, string lastName) { // add member to database sender.Send("New member registered!"); } }
نحوه استفاده از کلاس Members به صورت زیر است:
var members = new Members(new EmailManager()); members.Register("Hossein", "Ahmadi");
حال اگر بخواهیم نوع ارسال پیام را به پیامک تغییر دهیم تنها کافیست نوع شئ ارسالی به سازنده کلاس Members را تغییر دهیم:
var members = new Members(new SmsManager()); members.Register("Hossein", "Ahmadi");
به این تکنیک، DI یا Dependency Injection گفته می شود. ترکیب IoC و DI کمک زیادی به شما در نوشتن کدهایی می کنند که به راحتی قابلیت تغییر و به روز رسانی دارند. در این مقاله با یکی از کاربردهای بسیار مهم interface ها آشنا شدید. کتابخانه های آماده ای برای IoC و DI وجود دارند که به آنها IoC Container نیز گفته می شوند که روند DI رو برای شما به عنوان یک برنامه نویس بسیار راحت تر می کنند. در یک فیلم آموزشی در مورد IoC Container ها به تفصیل صحبت خواهیم کرد. در قسمت بعدی با مفهوم Type Casting و انواع Cast ها در زبان سی شارپ آشنا خواهیم شد.
منبع:programming.itpro.ir
- ۹۶/۰۲/۱۸