linkedin.com/in/masoud-mahmoodzadeh
مدیریت وابستگی در برنامه نویسی به زبون آدمیزاد
نوشتن همیشه یکی از علایقم بوده و هست و شاید اگر برنامه نویس نمی شدم یا شاید زودتر متوجه علاقه ام به نوشتن می شدم الان نویسنده بودم.
البته الان هم در حال نوشتنم با این فرق به جای اینکه واسه آدمها داستان بنویسم واسه موبایل ها برنامه مینویسم. خب سرتون بیشتر از این درد نیارم. تو این مطلب میخوایم با هم دیگه بگردیم دنبال جواب سوالات زیر:
- مفاهیم Principle و Pattern چیه؟ و فرقشون با هم چیه؟
- وابستگی چیه؟ چجوری میشه مدیریتش کرد؟
- اگه وابستگی ها رو مدیریت کنیم چه فایده ای داره ؟ و اگه مدیریت نکنیم چه ضرری داره؟
اول از همه بریم با دو مفهوم Principle و Pattern آشنا بشیم:
مفهوم Principle به قواعد و دستورالعمل های کلی اشاره میکنه که بهمون کمک میکنه طراحی اصولی و بهینه ای برای نرم افزارمون داشته باشیم. Principle ها چشم انداز هستن و افق و دوردست رو بهمون نشون میدن Principle ها یک یا چند هدف مشخص رو دنبال میکنن Principle ها در مورد اینکه چجوری میشه به اون هدف رسید راهکاری ارائه نمیده و بیشتر شبیه نصیحت هستن نصیحت هایی که اگه بهش عمل کنیم عاقبت به خیر میشیم و اگه رعایت نکنیم دچار خسر الدنیا و الآخرت میشیم.
اگه بخوایم چندتا مثال بزنیم میتونیم به SOLID و ِDRY و KISS و YAGNI و ... اشاره کنیم.
خب یه سوال؟؟!!
اگه Principle ها در مورد نحوه پیاده سازی و رسیدن به هدفش، راهکاری ارائه نمیده پس چجوری باید پیاده سازیش کرد؟
اینجاست که Pattern ها به کمکمون میان. Pattern ها در مورد رسیدن به اون هدفی که Principle ازش صحبت میکنه راهکار دقیق و عملی میده. مثال هاش هم میشه همون Design pattern ها معروفی که در موردش میدونین.
تو دنیای واقعی چه مثالی میتونیم بزنیم؟
فرض بگیریم یه زمینی دارین و میخواین اونجا یه برج بسازین . به تیم مهندسی میگین که میخوام اینجا یه برجی 20 طبقه ساخته بشه، خواسته شما میشه همون هدف Principle و روش هایی و مشاوره هایی که تیم مهندسی بهتون ارائه میده میشه همون Pattern
از اونجایی که در برنامه نویسی شی گرا زیاد پیش میاد که کلاس ها با هم در تعامل باشن و بهم نیاز و وابستگی داشته باشن یکی از چالش های مهم برنامه نویس ها، مدیریت این وابستگی هاست. اگه وابستگی ها مدیریت شده نباشه کدهامون دچار درهم تنیدگی و پیچیدگی میشه که باعث میشه کدهای ناخوانا، غیر قابل توسعه و با هزینه بالای نگهداری، داشته باشیم.
چند Principle و Pattern هست که بهمون کمک میکنه وابستگی رو مدیریت کنیم:
- DIP = Dependency Inversion Principle
- IoC = Inversion of Control Principle
- DI = Dependency Injection Pattern
که از این جمع DIP و IoC میشن Principle و DI میشه Pattern . اسماش سخت به نظر میرسه ولی خیلی مفاهیم سختی نیست.
از Dependency Inversion Principle شروع می کنیم
این اصل میگه که :
ماژول های سطح بالا و ماژول های سطح پایین نباید به هم وابسته باشن بلکه هر دو باید به انتزاعات وابسته باشن و خود انتزاعات نباید درگیر پیاده سازی بشه.
احتمالا واست سوال پیش اومده که:
- ماژول سطح بالا چیه؟
- ماژول سطح پایین چیه؟
- انتزاعات چیه؟
فرض بگیر در برنامه ات برای فراموشی رمز عبور از سامانه ارسال پیامک استفاده می کنی این سامانه ارسال پیامک یک ماژول سطح پایین هست و بخشی از برنامه ات که از این سامانه پیامکی استفاده می کنه میشه ماژول سطح بالا.
ماژول سطح پایین اون بخشیه که زیر ساختیه و بقیه ازش استفاده میکنن و ماژول سطح بالا بخشیه که از ماژول سطح پایین استفاده میکنه.
پس تقریبا می تونیم بگیم third party libraryها، دیتابیس، سرویس های خارجی برنامه،همه به نوعی ماژول سطح پایین هستن که زیر ساخت های برنامه رو شامل میشن.
میرسیم به انتزاعات
همون طور که میدونید انتزاعات یا به قول این خارجکی ها Abstraction در کنار Polymorphism و Inheritance و Encapsulation ، مفاهیم بنیادی شی گرایی را تشکیل میدن.
انتزاع یک مفهوم کلی هست که وقتی ازش صحبت میکنیم صرفا در مورد ویژگی های کلی اون مفهوم یه چیزهایی میدونیم ولی از جزئیاتش چیزی نمیدونیم.
مثلا وقتی از میز صحبت میکنیم میز یک مفهوم انتزاعیه با شنیدن کلمه میز در ذهنت تصویری کلی از یک میز شکل میگیره ولی در مورد جزئیات دقیقش چیزی نمی دونی که میز چوبیه؟ فلزیه؟ شیشه ای؟ پلاستیکه؟ واسه اداره استفاده میشه؟ یا خونه؟ یا مغازه؟ پایه هاش چجوریه؟ کشو داره؟ نداره؟
خودمونی بخوام بگم انتزاع معادل تصویره، معادل ذهنیته مثل یک عکس میمونه. وقتی به یک عکس از کوه دماوند نگاه میکنی صرفا داری کلیاتش رو میبینی نمیتونی لمسش کنی نمیتونی بفهمی الان اونجا آب و هواش چجوریه؟
با این چیزهایی که گفتم میتونیم چند تا انتزاع دیگه هم مثال بزنیم:
- اتومبیل
- خونه
- مسافرت
- موبایل
نقطه مقابل abstraction مفهوم concrete هست کلمه concrete به معنای بتن هست بتن هم نماد یه جسم سخته پس میتونیم نتیجه بگیریم که concrete به معنای ملموس بودن، واقعی بودن، وجود خارجی داشتن، هست. abstraction کلی هست ولی concrete ملموس، واقعی و شامل جزئیاته.
یه بار دیگه تعریف DIP رو با هم بخونیم:
ماژول های سطح بالا و ماژول های سطح پایین نباید به هم وابسته باشن بلکه هر دو باید به انتزاعات وابسته باشن و خود انتزاعات نباید درگیر پیاده سازی بشه.
الان دیگه احتمالا این مفهوم واست واضح تر شده.
توی زندگی روزمره مون از این DIP استفاده می کنیم؟
وقتی میخوای گوشیتو شارژ کنی گوشیتو به پریز برق توی دیوار که لحیم نمیکنی!! میای از آداپتور استفاده می کنی. برق توی دیوار میشه ماژول سطح پایین، گوشیت میشه ماژول سطح بالا، آداپتورت هم میشه اون انتزاعی که وابستگی بین برق توی دیوار و گوشی رو مدیریت میکنه.
راستی داشت یادم میرفت این اصل همون حرف D از اصول پنج گانه SOLID هست.
خب در امتداد اصل Dependency Inversion میرسیم به یک اصل دیگه که به نام Inversion of Control
که خلاصه بهش میگن IoC که در فارسی، وارونگی کنترل یا بعضا وارونگی مسئولیت هم بهش میگن
منظور از کنترل چیه؟
هر مسئولیتی که این کلاس به غیر از مسئولیت اصلی اش داره
حالا اصل IoC چی میگه؟
میگه که هر مسئولیتی که یک کلاس به غیر از مسئولیت اصلی اش داره باید واگذار بشه به یکی کلاس یا ماژول و ... دیگه
مثال کلاسیکی که واسه این اصل میزنن اینه که:
فرض بگیرین شما میخواین با ماشینت بری سرکار، یه راهش اینه خودت بشینی پشت فرمون و گازش رو بگیری بری شرکت، یه راهش اینه یه راننده داشته باشی اون ببرتت شرکت
در حالت دوم اصل IoC رعایت شده و مسئولیت رانندگی رو به یه نفر دیگه واگذار کردی و خودت میتونی به یه کار دیگه برسی
اصل IoC شامل دو روش میشه:
- کنترل بر جریان برنامه
- کنترل بر ساخت اشیا وابسته
اول کنترل جریان برنامه رو توضیح بدم
فرض بگیرین میخوایم یه برنامه تحت دسکتاپ بنویسیم و نام و نام خانوادگی، شماره همراه و رمز عبور از کاربر بگیریم و ثبت نام واسش انجام بدیم
اگه قرار باشه این اطلاعات رو از کاربر در محیط های CLI مثل Cmd و PowerShell و ... بگیریم تابع main اون برنامه اجرا میشه و به ترتیب اطلاعات رو از کاربر میگیره و در نهایت اطلاعات رو ثبت میکنه و تمام
در این حالت تابع main همه کاره هست و کنترل جریان برنامه دست گرفته و کاربر حق انتخابی که ترتیب پر کردن اطلاعات چجوری باشه نداره.
حالا اگر بخوایم اصل IoC را رعایت کنیم چکار باید بکنیم؟
باید از GUI یا همون رابط های گرافیکی مثل WinForm و WPF و ... استفاده کنیم در این حالت کنترل اجرای برنامه از طریق GUI مدیریت میشه و کاربر در ترتیب پر شدن اطلاعات نقش داره.
خب بریم سراغ کنترل بر ساخت اشیای وابسته
گفتیم که در برنامه نویسی شی گرا زیاد پیش میاد که کلاس ها با هم تعامل داشته باشن و به هم نیازمند و وابسته باشن IoC در این مورد تاکید داره که یک کلاس باید روی وظیفه اصلی خودش تمرکز کنه و نباید خودش رو درگیر تامین وابستگی ها کنه این وابستگی ها باید توسط یکی دیگه انجام بشه.
خب حالا که فهمیدیم Dependency inversion و Inversion of Control چی هست بریم سراغ پیاده سازی این اصول.
روش های مختلفی برای پیاده سازی این اصول هست مثل:
- Dependency Injection
- Strategy
- Service Locator
- Factory
در این مطلب Dependency Injection رو با هم مرور کنیم:
پترن Dependency Injection یه روش برای مدیریت و کاهش وابستگی بین کلاس هاست. Dependency Injection از دو کلمه تشکیل شده:
- Dependency :
گفتیم که کلاس ها برای انجام دادن وظیفه ای که دارن با هم تعامل دارن و بهم نیاز دارن این تعاملات و نیازمندی ها رو بهش میگیم Dependency
- Injection :
یه روش واسه فراهم کردن و تامین کردن وابستگی های یه کلاس.
وقتی کلاس A به کلاس B نیاز داره و بهش وابسته هست پس باید به آبجکتی از کلاس B دسترسی داشته باشه. یه راهش اینه داخل کلاس A خودش یه آبجکت از کلاس B بسازه و ازش استفاده کنه و یه راهش اینه که آبجکت B بیرون از کلاس A ساخته بشه و به کلاس A داده بشه.
روش اول باعث میشه دو کلاس خیلی چفت و محکم بهم وابسته باشن و بعدها واسمون مشکل ساز بشه
ولی روش دوم که میشه همون تزریق وابستگی یا Dependency injection باعث میشه وابستگی بین دو کلاس کمتر و بهینه تر باشه.
با تزریق وابستگی کلاس A روی وظیفه اصلی خودش تمرکز داره و وظیفه ساخته شدن و تامین کلاس B رو به یه کلاس دیگه واگذار میکنه و این همون چیزیه که IoC بهش تاکید داره.
از سه روش میتونیم تزریق وابستگی رو انجام بدیم:
- Constructor injection
- Setter injection (property injection)
- Method injection (Interface injection)
روش اول از طریق Constructor وابستگی بهش داده میشه:
روش دوم از طریق Setter method وابستگی بهش تزریق میشه:
روش سوم یه اینترفیس رو از طریق یه تابع بهش تزریق میکنه:
فرض بگیرین قراره یه نرم افزار برای مدیریت یه کافی شاپ داشته باشیم
کافی شاپ انواع مختلفی از نوشیدنی ها رو ارائه میده و آماده شدن هر نوشیدنی روش خاص خودشو داره پس برای هر نوشیدنی یک کلاس مجزا مینویسیم همه این نوشیدنی ها یه اینترفیس IDrink داره که آماده شدن نوشیدنی رو پیاده سازی میکنه.
کلاس CoffeeShop باید بتونه هر نوع نوشیدنی رو آماده کنه پس نباید به یک نوع خاص از نوشیدنی وابسته باشه و به همین دلیله که ورودی متد prepareDrink یه IDrink دریافت میکنه و اینجوری میشه هر نوعی از نوشیدنی رو به کلاس CoffeeShop تزریق کرد.
اگه دقت کرده باشین کلاس CoffeeShop یک ماژول سطح بالا هست و کلاس های Espresso و HotChocolate ماژول های سطح پایین هستن که هر کدومشون به یه انتزاعی وابسته هستن پس میتونیم نتیجه بگیریم که اصل Dependency inversion رو در این مثال رعایت کردیم.
کلاس CoffeeShop عمل آماده شدن نوشیدنی رو هم به کلاس همون نوشیدنی واگذار کرده پس میتونیم نتیجه بگیریم که اصل Inversion of Control هم در این مثال رعایت کردیم.
و به نظرم میتونیم نتیجه بگیریم که interface injection روشی برای تزریق یه رفتار به یه کلاس هست.
یه مفهوم دیگه هم هست به نام IoC Container
این IoC Container فریمورکی برای مدیریت Dependency injection هست با IoC Container میشه تنظیماتی انجام داد که وقتی برنامه به یه نوع خاصی از Interface نیاز داشت کلاس مشخصی بهش داده بشه.
برای اندروید از Dagger و Hiltو Koin استفاده میشه و شنیدم که میگن برای .NET از Autofac و Ninject و StructureMap استفاده میشه.
خب پس متوجه شدیم:
مفاهیم principle و pattern چی هست و فرقشون با هم چیه
وابستگی ها رو فهمیدم چیه؟ و با دو تا اصل در مورد وابستگی ها آشنا شدیم
تزریق وابستگی رو متوجه شدیم چیه و چجوری میشه پیاده سازی اش کرد
و فهمیدیم IoC Container چیه
مطلبی دیگر از این انتشارات
کاربرد دو کلید esc و del در نرم افزار اکسل
مطلبی دیگر از این انتشارات
سیستم عامل Qubes چیست؟
مطلبی دیگر از این انتشارات
تنتن در کشور شوراها