الگوهای طراحی هدیه خداوند به توسعه دهندگان نرم افزار است که شامل تکنیک هایی است که تکرار کد را به حداقل می رساند از اتصالات زیاد جلوگیری میکند و یک روش استاندارد مشترک کد نویسی و یک راه حل عمومی برای وضعیت های تکرار شونده هنگام توسعه نرم افزار فراهم می کند. در این متن، ما با یک الگوی طراحی به نام VIPER برای توسعه نرم افزار iOS آشنا خواهیم شد. (View, Interactor, Presenter, Entity and Router)
پیشنیازها: قبل از شروع VIPER، اطمینان پیداکنید که در رابطه با الگوهای طراحی معماری و الگوهای Delegation اطلاعات کافی داشته باشید.
الگوی VIPER یک الگوی طراحی است که براساس الگوی “تفکیک نگرانی” (separation of concern) را کار میکند. بسیار شبیه به MVP و MVC از رویکرد ماژولار پیروی می کند. هر ویژگی (feature) یک ماژول. هر ماژول VIPER پنج (گاهی اوقات ۴) کلاس مختلف با نقشهای متمایز دارد. هیج کلاسی پایش را فراتر از هدف تعیین شدهاش نخواهد گذاشت. این کلاسها از یکدیگر پیروی میکنند.
در زیر یک نمای ساده ای از VIPER را مشاهده میکنید.
یک پروژه ساده آماده شده تا به کمک آن بتوان VIPER را شرح داد. این پروژه روی GitHub در دسترس است. یک اپلیکیشن خیلی ساده که سرخط اخبار را از یک API خارجی واکشی و نمایش میدهد.
الگوی Viper یک معماری delegation محور است. به همین دلیل است که ارتباطات بین لایههای مختلف با استفاده از delegation انجام میشود. یک لایه، لایه دیگر را با استفاده از پروتکل فراخوانی میکند. فراخوانی لایه، یک تابع از پروتکل را فراخوانی میکند. لایه گوش دهنده با پروتکل مطابقت دارد و تابع را پیاده سازی میکند.
در ادامه به توصیف نحوه پیاده سازی VIPER در یکی از پروژه های نمونه میپردازیم.
برای هر پروتکل نیاز به ساخت یک فایل مجزا است.
برای نامگذاری پروتکلها از یک قرارداد نام گذاری پیروی شده است، به عنوان مثال viewToPresenterProtocol. پس از این نام گذاری در مییابیم که این یک پروتکل است که توسط presenter برای گوش دادن به آنچه view میگوید، ساخته میشود.
در این پروتکل presenter فراخوانی میکند، View گوش میدهد. Presenter یک مرجع از این پروتکل برای دسترسی به View دریافت میکند.
در این پروتکل View فراخوانی میکند، Presenter گوش میدهد.
در این پروتکل Interactor فراخوانی میکند، Presenter گوش میدهد.
در این پروتکل Presenter فراخوانی میکند، Interactor گوش میدهد.
در این پروتکل Presenter فراخوانی میکند، Router گوش میدهد.
باید بدانیم که View یک مرجعی از ViewToPresenterProtocol برای دسترسی به Presenter دارد که آن را با PresenterToViewProtocol مطابقت میدهد. در تابع viewDidLoad آن تابع updateView پروتوکل را فراخوانی میکند.
//View var presenter: ViewToPresenterProtocol? override func viewDidLoad() { super.viewDidLoad() presenter?.updateView() }
از طرف دیگر Presenter با ViewToPresenterProtocol مطابقت میدهد و تابع updateView را پیاده سازی میکند.
//Presenter var interactor: PresentorToInteractorProtocol? func updateView() { interactor?.fetchLiveNews() }
داخل updateView()، Presenter به Interactor میگوید تا تعدادی دادهی اخبار زنده را واکشی کند.
خود Interactor با PresenterToInteractorProtocol تطابق دارد. پس تابع fetchLiveNews را پیاده سازی میکند. این تابع سعی میکند تا یک فراخوانی شبکهای ایجاد کند و داده را واکشی کند که برای دسترسی به Presenter یک مرجع از InteractorToPresenterProtocol دارد.
//Interactor var presenter: InteractorToPresenterProtocol?
اگر فراخوانی شبکه با موفقیت داده را واکشی کند، تابع زیر فراخوانی میشود.
//Interactor self.presenter?.liveNewsFetched(news: (arrayObject?[0])!)
و اگر نه تابع زیر:
//Interactor self.presenter?.liveNewsFetchedFailed()
حالا presenter همچنین با InteractorToPresenterProtocol تطابق دارد و این توابع را پیاده سازی میکند.
//presenter func liveNewsFetched(news: LiveNewsModel) { view?.showNews(news: news) } func liveNewsFetchedFailed(){ view?.showError() }
پس به View میگوید که یا اخبار را نشان دهد و یا خطا نشان دهد.
حالا View با PresenterToViewProtocol تطابق دارد، بنابراین توابع showNews() و showError() را پیاده سازی میکند. در این دو تابع View با دادههای واکشی شده یا خطا پر میشوند.
در بالا در بخش جربان اپلیکیشن در رابطه با entity بحث نشد. Entity به صورت مستقیم با جریان اپلیکیشن در ارتباط نیست. اما آن یک قسمت حیاتی برای Interactor است. لایه Entity یک مدل است که interact برای ساخت objectها از دادههای واکشی شده از آن استفاده میکند.
بخش Router از سیم کشی یک اپلیکیشن نگهداری میکند. تغییر صفحه در یک برنامه مساله بسیار پایهای است. در VIPER، لایهی Router مسئولیت اجرای آن را برعهده دارد.
قبلا به این نتیجه رسیدیم که در معماری VIPER برای هر بخش عملیاتی به یک ماژول مجزا احتیاج داریم و هر ماژول دارای ۵ لایه است. یک Presenter، Router را برای ساخت یک ماژول جدید فراخوانی میکند. پس از آن Router تمام کلاس لایه را مقداردهی میکند و ماژول را برمیگرداند.
در پروژه نمونه من هیچ تغییر ماژول درون برنامهای وجود ندارد. اما مسیریابی زمانه که اپلیکیشن برای اولین بار اجرا شود، اتفاق میافتد. پس در AppDelegate در تابع didFinishLaunchigWithOptions تابع createModule درون Router فراخوانی میشود که یک ماژول برمیگرداند. کلاس UIWindow، View ماژول جدید را نمایش میدهد.
الگوی VIPER از یک معماری تمیز پیروی میکند که هر ماژول از دیگری متمایز و ایزوله است. پس به منظور انجام تغییرات یا رفع باگ به راحتی میتوان تنها ماژول مورد نظر را به روز کرد. همچنین برای داشتن رویکرد ماژولار، VIPER محیط مناسبی به منظور انجام َUnit Test فراهم میکند. از آنجایی که هر ماژول از دیگری مجزا است، از جفت شدگیهای کم (low coupling) به خوبی جلوگیری میکند. پس تقسیم کار بین چند توسعه دهنده به راحتی امکانپذیر است.
الگوی VIPER باید زمانی که نیازمندیهای اپلیکیشن به خوبی شکل گرفتهاند استفاده شود. کار با نیازمندیهای همیشه درحال تغییر ممکن است سردرگمی و کد کثیف ایجاد کند. پس نباید از آن در پروژههای کوچکی که MVP و MVC نیازشان را برطرف میکند، استفاده کرد. همچنین VIPER باید زمانی استفاده شود که تمامی توسعهدهندگان درگیر پروژه الگوی آن را درک کرده باشند.
اگر میخواهید از VIPER در یک پروژه استفاده کنید، هوشمندانهترین کار این است که از یک تولید کننده ساختار ماژول خودکار استفاده کنید. در غیر اینصورت ساخت فایل برای ماژول ها بسیار طاقت فرساست. چند تولید کننده آنلاین برای این منظور وجود دارد:
به مانند تمام الگوهای طراحی دیگر، VIPER نیز خود بیانگر (self-explanatory) است. اگر شخصی بخواهد کل تصویر راه بفهمد باید دستش را آلوده کند. نصیحت من این است که ابتدا با یک اپلیکیشن ساده شروع کنید و کل مراحل را از منابع آنلاین بخوانید.
موفق باشید
منبع اصلی: Medium
منابع: