راهنمای معماری اپلیکیشن

معماری برنامه، پایه و اساس یک برنامه اندرویدی با کیفیت بالا است. یک معماری خوش‌تعریف شما را قادر می‌سازد تا یک برنامه مقیاس‌پذیر و قابل نگهداری ایجاد کنید که بتواند با اکوسیستم رو به گسترش دستگاه‌های اندرویدی، از جمله تلفن‌ها، تبلت‌ها، دستگاه‌های تاشو، دستگاه‌های ChromeOS، نمایشگرهای خودرو و واقعیت افزوده (XR) سازگار شود.

ترکیب برنامه

یک برنامه اندروید معمولی از چندین کامپوننت برنامه مانند سرویس‌ها ، ارائه‌دهندگان محتوا و گیرنده‌های پخش تشکیل شده است. شما این کامپوننت‌ها را در مانیفست برنامه خود اعلام می‌کنید.

رابط کاربری یک برنامه نیز یک کامپوننت است. از نظر تاریخی، رابط‌های کاربری با استفاده از چندین اکتیویتی ساخته می‌شدند. با این حال، برنامه‌های مدرن از معماری تک‌فعالیتی استفاده می‌کنند. یک Activity واحد به عنوان ظرفی برای صفحاتی که به صورت قطعات یا مقاصد Jetpack Compose پیاده‌سازی شده‌اند، عمل می‌کند.

فاکتورهای فرم چندگانه

برنامه‌ها می‌توانند روی چندین فرم فاکتور اجرا شوند، از جمله نه تنها تلفن‌ها، بلکه تبلت‌ها، دستگاه‌های تاشو، دستگاه‌های ChromeOS و موارد دیگر. یک برنامه نمی‌تواند جهت عمودی یا افقی را فرض کند. تغییرات پیکربندی، مانند چرخش دستگاه یا تا کردن و باز کردن یک دستگاه تاشو، برنامه شما را مجبور به بازسازی رابط کاربری خود می‌کند که بر داده‌ها و وضعیت برنامه تأثیر می‌گذارد.

محدودیت‌های منابع

دستگاه‌های تلفن همراه - حتی دستگاه‌های با صفحه نمایش بزرگ - از نظر منابع محدود هستند، بنابراین در هر زمانی، سیستم عامل ممکن است برخی از فرآیندهای برنامه را متوقف کند تا فضای کافی برای فرآیندهای جدید فراهم شود.

شرایط پرتاب متغیر

در یک محیط با منابع محدود، اجزای برنامه شما می‌توانند به صورت جداگانه و خارج از ترتیب راه‌اندازی شوند؛ علاوه بر این، سیستم عامل یا کاربر می‌تواند در هر زمانی آنها را از بین ببرد. در نتیجه، هیچ داده یا وضعیت برنامه را در اجزای برنامه خود ذخیره نکنید. اجزای برنامه شما باید مستقل و خودکفا باشند.

اصول معماری مشترک

اگر نمی‌توانید از کامپوننت‌های برنامه برای ذخیره داده‌ها و وضعیت برنامه استفاده کنید، چگونه باید برنامه خود را طراحی کنید؟

با افزایش حجم برنامه‌های اندروید، تعریف معماری که به برنامه اجازه مقیاس‌پذیری بدهد، بسیار مهم است. یک معماری برنامه که به خوبی طراحی شده باشد، مرزهای بین بخش‌های برنامه و مسئولیت‌هایی که هر بخش باید داشته باشد را تعریف می‌کند.

تفکیک دغدغه‌ها

معماری برنامه خود را طوری طراحی کنید که از چند اصل خاص پیروی کند.

مهم‌ترین اصل، جداسازی دغدغه‌ها است. نوشتن تمام کد در یک Activity یا یک Fragment یک اشتباه رایج است.

نقش اصلی یک Activity یا Fragment ، میزبانی رابط کاربری برنامه شماست. سیستم عامل اندروید چرخه حیات آنها را کنترل می‌کند و مرتباً در پاسخ به اقدامات کاربر مانند چرخش صفحه یا رویدادهای سیستمی مانند کمبود حافظه، آنها را از بین می‌برد و دوباره ایجاد می‌کند.

این ماهیت زودگذر، آنها را برای نگهداری داده‌ها یا وضعیت برنامه نامناسب می‌کند. اگر داده‌ها را در یک Activity یا Fragment ذخیره کنید، آن داده‌ها هنگام ایجاد مجدد کامپوننت از بین می‌روند. برای اطمینان از ماندگاری داده‌ها و ارائه یک تجربه کاربری پایدار، وضعیت را به این اجزای UI واگذار نکنید.

طرح‌بندی‌های تطبیقی

برنامه شما باید به خوبی تغییرات پیکربندی، مانند تغییر جهت دستگاه یا تغییر در اندازه پنجره برنامه را مدیریت کند. طرح‌بندی‌های متعارف تطبیقی ​​را پیاده‌سازی کنید تا یک تجربه کاربری بهینه را در انواع فرم فاکتورها ارائه دهید.

رابط کاربری را از مدل‌های داده‌ای هدایت کنید

اصل مهم دیگر این است که شما باید رابط کاربری خود را از مدل‌های داده، ترجیحاً مدل‌های پایدار، هدایت کنید. مدل‌های داده، داده‌های یک برنامه را نشان می‌دهند. آن‌ها مستقل از عناصر رابط کاربری و سایر اجزای برنامه شما هستند. این بدان معناست که آن‌ها به چرخه عمر رابط کاربری و اجزای برنامه وابسته نیستند، اما هنگامی که سیستم عامل فرآیند برنامه را از حافظه حذف می‌کند، همچنان از بین خواهند رفت.

مدل‌های پایدار به دلایل زیر ایده‌آل هستند:

  • اگر سیستم عامل اندروید برای آزاد کردن منابع، برنامه شما را از بین ببرد، کاربران اطلاعات خود را از دست نمی‌دهند.

  • برنامه شما در مواردی که اتصال شبکه قطع یا در دسترس نباشد، به کار خود ادامه می‌دهد.

معماری برنامه خود را بر اساس کلاس‌های مدل داده بنا کنید تا برنامه شما قوی و قابل آزمایش باشد.

تنها منبع حقیقت

وقتی یک نوع داده جدید در برنامه شما تعریف می‌شود، باید یک منبع واحد حقیقت (SSOT) به آن اختصاص دهید. SSOT مالک آن داده است و فقط SSOT می‌تواند آن را تغییر دهد یا جهش دهد. برای دستیابی به این هدف، SSOT داده‌ها را با استفاده از یک نوع تغییرناپذیر در معرض نمایش قرار می‌دهد؛ برای تغییر داده‌ها، SSOT توابع را در معرض نمایش قرار می‌دهد یا رویدادهایی را دریافت می‌کند که انواع دیگر می‌توانند آنها را فراخوانی کنند.

این الگو مزایای متعددی دارد:

  • تمام تغییرات مربوط به یک نوع داده خاص را در یک مکان متمرکز می‌کند.
  • از داده‌ها محافظت می‌کند تا انواع دیگر نتوانند آن را دستکاری کنند.
  • تغییرات در داده‌ها را قابل ردیابی‌تر می‌کند و بنابراین، تشخیص اشکالات آسان‌تر می‌شود.

در یک برنامه‌ی آفلاین، منبع حقیقت برای داده‌های برنامه معمولاً یک پایگاه داده است. در برخی موارد دیگر، منبع حقیقت می‌تواند یک ViewModel باشد.

جریان داده یک طرفه

اصل منبع واحد حقیقت اغلب با الگوی جریان داده یک‌طرفه (UDF) استفاده می‌شود. در UDF، حالت فقط در یک جهت، معمولاً از مؤلفه والد به مؤلفه فرزند، جریان می‌یابد. رویدادهایی که جریان داده را در جهت مخالف تغییر می‌دهند.

در اندروید، حالت یا داده‌ها معمولاً از انواع با دامنه بالاتر سلسله مراتب به انواع با دامنه پایین‌تر جریان می‌یابند. رویدادها معمولاً از انواع با دامنه پایین‌تر آغاز می‌شوند تا زمانی که به SSOT برای نوع داده مربوطه برسند. به عنوان مثال، داده‌های برنامه معمولاً از منابع داده به رابط کاربری جریان می‌یابند. رویدادهای کاربر مانند فشردن دکمه از رابط کاربری به SSOT جریان می‌یابند که در آن داده‌های برنامه تغییر یافته و در یک نوع تغییرناپذیر نمایش داده می‌شوند.

این الگو، سازگاری داده‌ها را بهتر حفظ می‌کند، کمتر مستعد خطا است، اشکال‌زدایی آن آسان‌تر است و تمام مزایای الگوی SSOT را ارائه می‌دهد.

با توجه به اصول معماری رایج، هر برنامه باید حداقل دو لایه داشته باشد:

  • لایه رابط کاربری: داده‌های برنامه را روی صفحه نمایش می‌دهد.
  • لایه داده: شامل منطق تجاری برنامه شما است و داده‌های برنامه را در معرض نمایش قرار می‌دهد.

شما می‌توانید یک لایه اضافی به نام لایه دامنه اضافه کنید تا تعاملات بین لایه‌های رابط کاربری و داده ساده‌سازی و قابل استفاده مجدد شود.

در یک معماری برنامه معمولی، لایه رابط کاربری، داده‌های برنامه را از لایه داده یا از لایه دامنه اختیاری که بین لایه رابط کاربری و لایه داده قرار دارد، دریافت می‌کند.
شکل ۱. نمودار معماری یک برنامه‌ی کاربردی معمولی.

معماری مدرن اپلیکیشن

یک معماری مدرن اپلیکیشن اندروید از تکنیک‌های زیر (و موارد دیگر) استفاده می‌کند:

  • معماری تطبیقی ​​و لایه‌ای
  • جریان داده یک‌طرفه (UDF) در تمام لایه‌های برنامه
  • لایه رابط کاربری با نگهدارنده‌های وضعیت برای مدیریت پیچیدگی رابط کاربری
  • کوروتین‌ها و جریان‌ها
  • بهترین شیوه‌های تزریق وابستگی

برای اطلاعات بیشتر، به توصیه‌هایی برای معماری اندروید مراجعه کنید.

لایه رابط کاربری

نقش لایه رابط کاربری (یا لایه نمایش ) نمایش داده‌های برنامه روی صفحه است. هر زمان که داده‌ها تغییر کنند، چه به دلیل تعامل کاربر (مانند فشار دادن یک دکمه) و چه به دلیل ورودی خارجی (مانند پاسخ شبکه)، رابط کاربری باید به‌روزرسانی شود تا تغییرات را منعکس کند.

لایه رابط کاربری شامل دو نوع ساختار است:

  • عناصر رابط کاربری که داده‌ها را روی صفحه نمایش می‌دهند. شما این عناصر را با استفاده از توابع Jetpack Compose برای پشتیبانی از طرح‌بندی‌های تطبیقی ​​می‌سازید.
  • نگهدارنده‌های وضعیت (مانند ViewModel ) که داده‌ها را نگه می‌دارند، آن‌ها را در معرض رابط کاربری قرار می‌دهند و منطق را مدیریت می‌کنند
در یک معماری معمول، عناصر رابط کاربری لایه رابط کاربری به دارندگان وضعیت (state holders) وابسته هستند که به نوبه خود به کلاس‌هایی از لایه داده یا لایه دامنه اختیاری (optional domain layer) وابسته هستند.
شکل ۲. نقش لایه رابط کاربری در معماری برنامه.

برای رابط‌های کاربری تطبیقی، نگهدارنده‌های وضعیت مانند اشیاء ViewModel ، وضعیت رابط کاربری را که با کلاس‌های مختلف اندازه پنجره تطبیق می‌یابد، نمایش می‌دهند. می‌توانید از currentWindowAdaptiveInfo() برای استخراج این وضعیت رابط کاربری استفاده کنید. سپس کامپوننت‌هایی مانند NavigationSuiteScaffold می‌توانند از این اطلاعات برای جابجایی خودکار بین الگوهای ناوبری مختلف (به عنوان مثال، NavigationBar ، NavigationRail یا NavigationDrawer ) بر اساس فضای صفحه نمایش موجود استفاده کنند.

برای کسب اطلاعات بیشتر، به صفحه لایه رابط کاربری مراجعه کنید.

لایه داده

لایه داده یک برنامه شامل منطق تجاری است. منطق تجاری چیزی است که به برنامه شما ارزش می‌دهد - این شامل قوانینی است که نحوه ایجاد، ذخیره و تغییر داده‌ها را در برنامه شما تعیین می‌کند.

لایه داده از مخازنی تشکیل شده است که هر کدام می‌توانند شامل صفر تا تعداد زیادی منبع داده باشند. شما باید برای هر نوع داده‌ای که در برنامه خود مدیریت می‌کنید، یک کلاس مخزن ایجاد کنید. به عنوان مثال، ممکن است یک کلاس MoviesRepository برای داده‌های مربوط به فیلم‌ها یا یک کلاس PaymentsRepository برای داده‌های مربوط به پرداخت‌ها ایجاد کنید.

در یک معماری معمول، مخازن لایه داده، داده‌ها را برای بقیه برنامه فراهم می‌کنند و به منابع داده وابسته هستند.
شکل ۳. نقش لایه داده در معماری برنامه.

کلاس‌های مخزن (Repository) مسئول موارد زیر هستند:

  • افشای داده‌ها به بقیه‌ی برنامه
  • متمرکز کردن تغییرات در داده‌ها
  • حل تعارض بین منابع داده چندگانه
  • انتزاع منابع داده از بقیه برنامه
  • شامل منطق کسب و کار

هر کلاس منبع داده باید مسئولیت کار با تنها یک منبع داده را داشته باشد، که می‌تواند یک فایل، یک منبع شبکه یا یک پایگاه داده محلی باشد. کلاس‌های منبع داده، پلی بین برنامه و سیستم برای عملیات داده هستند.

برای کسب اطلاعات بیشتر، به صفحه لایه داده مراجعه کنید.

لایه دامنه

لایه دامنه یک لایه اختیاری بین لایه‌های رابط کاربری و داده است.

لایه دامنه مسئول کپسوله‌سازی منطق تجاری پیچیده یا منطق تجاری ساده‌تری است که توسط چندین مدل نمایش مورد استفاده مجدد قرار می‌گیرد. لایه دامنه اختیاری است زیرا همه برنامه‌ها این الزامات را ندارند. فقط در صورت نیاز از آن استفاده کنید، به عنوان مثال، برای مدیریت پیچیدگی یا افزایش قابلیت استفاده مجدد.

وقتی این لایه گنجانده می‌شود، لایه دامنه اختیاری وابستگی‌هایی را به لایه رابط کاربری ارائه می‌دهد و به لایه داده وابسته است.
شکل ۴. نقش لایه دامنه در معماری برنامه.

کلاس‌های موجود در لایه دامنه معمولاً موارد استفاده یا interactor نامیده می‌شوند. هر مورد استفاده باید مسئولیت یک عملکرد واحد را بر عهده داشته باشد. برای مثال، اگر چندین مدل نمایش برای نمایش پیام مناسب روی صفحه به مناطق زمانی متکی هستند، برنامه شما می‌تواند یک کلاس GetTimeZoneUseCase داشته باشد.

برای کسب اطلاعات بیشتر، به صفحه لایه دامنه مراجعه کنید.

مدیریت وابستگی‌های بین اجزا

کلاس‌های برنامه شما برای عملکرد صحیح به کلاس‌های دیگر وابسته هستند. می‌توانید از هر یک از الگوهای طراحی زیر برای جمع‌آوری وابستگی‌های یک کلاس خاص استفاده کنید:

  • تزریق وابستگی (DI) : تزریق وابستگی به کلاس‌ها اجازه می‌دهد تا وابستگی‌های خود را بدون ساخت آنها تعریف کنند. در زمان اجرا، کلاس دیگری مسئول ارائه این وابستگی‌ها است.
  • مکان‌یاب سرویس : الگوی مکان‌یاب سرویس، یک رجیستری فراهم می‌کند که در آن کلاس‌ها می‌توانند به جای ساخت وابستگی‌های خود، آنها را دریافت کنند.

این الگوها به شما امکان می‌دهند کد خود را مقیاس‌بندی کنید زیرا الگوهای روشنی برای مدیریت وابستگی‌ها بدون تکرار کد یا افزایش پیچیدگی ارائه می‌دهند. این الگوها همچنین به شما امکان می‌دهند به سرعت بین پیاده‌سازی‌های آزمایشی و عملیاتی جابجا شوید.

بهترین شیوه‌های عمومی

برنامه‌نویسی یک زمینه خلاقانه است و ساخت برنامه‌های اندروید نیز از این قاعده مستثنی نیست. روش‌های زیادی برای حل یک مشکل وجود دارد؛ شما ممکن است داده‌ها را بین چندین فعالیت یا قطعه کد تبادل کنید، داده‌های از راه دور را بازیابی کنید و آنها را به صورت محلی برای حالت آفلاین ذخیره کنید، یا هر تعداد سناریوی رایج دیگری را که برنامه‌های کاربردی با آن مواجه می‌شوند، مدیریت کنید.

اگرچه توصیه‌های زیر اجباری نیستند، اما در بیشتر موارد، پیروی از آنها باعث می‌شود کد شما قوی‌تر، قابل آزمایش‌تر و قابل نگهداری‌تر شود.

داده‌ها را در اجزای برنامه ذخیره نکنید.

از تعیین نقاط ورودی برنامه خود - مانند فعالیت‌ها، سرویس‌ها و گیرنده‌های پخش - به عنوان منابع داده خودداری کنید. نقاط ورودی فقط باید با سایر اجزا هماهنگ باشند تا زیرمجموعه‌ای از داده‌های مرتبط با آن نقطه ورودی را بازیابی کنند. هر جزء برنامه بسته به تعامل کاربر با دستگاه و ظرفیت سیستم، کوتاه‌مدت است.

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

اجزای برنامه شما باید تنها کلاس‌هایی باشند که به APIهای SDK فریم‌ورک اندروید مانند Context یا Toast متکی هستند. جدا کردن سایر کلاس‌ها در برنامه شما از اجزای برنامه، به تست‌پذیری کمک می‌کند و اتصال درون برنامه شما را کاهش می‌دهد.

مرزهای مشخصی از مسئولیت بین ماژول‌های برنامه خود تعریف کنید.

کدی که داده‌ها را از شبکه بارگذاری می‌کند، در چندین کلاس یا بسته در پایگاه کد خود پخش نکنید. به طور مشابه، چندین مسئولیت نامرتبط، مانند ذخیره‌سازی داده‌ها و اتصال داده‌ها، را در یک کلاس تعریف نکنید. پیروی از معماری برنامه توصیه شده کمک خواهد کرد.

تا حد امکان از هر ماژول کمترین میزان نوردهی را داشته باشید.

میانبرهایی ایجاد نکنید که جزئیات پیاده‌سازی داخلی را افشا می‌کنند. ممکن است در کوتاه‌مدت کمی زمان به دست آورید، اما احتمالاً با تکامل کدبیس خود، چندین برابر بدهی فنی متحمل خواهید شد.

روی هسته منحصر به فرد اپلیکیشن خود تمرکز کنید تا از سایر اپلیکیشن‌ها متمایز شود.

با نوشتن مکرر کدهای تکراری و تکراری، چرخ را از نو اختراع نکنید. در عوض، زمان و انرژی خود را روی چیزی که برنامه شما را منحصر به فرد می‌کند متمرکز کنید. اجازه دهید کتابخانه‌های Jetpack و سایر کتابخانه‌های توصیه شده، کدهای تکراری را مدیریت کنند.

از طرح‌بندی‌های متعارف و الگوهای طراحی برنامه استفاده کنید.

کتابخانه‌های Jetpack Compose رابط‌های برنامه‌نویسی کاربردی (API) قدرتمندی برای ساخت رابط‌های کاربری تطبیق‌پذیر ارائه می‌دهند. از طرح‌بندی‌های متعارف در برنامه خود برای بهینه‌سازی تجربه کاربری در فرم‌فکتورها و اندازه‌های مختلف نمایشگر استفاده کنید. گالری الگوهای طراحی برنامه را بررسی کنید تا طرح‌بندی‌هایی را انتخاب کنید که برای موارد استفاده شما بهترین عملکرد را دارند.

حفظ وضعیت رابط کاربری در طول تغییرات پیکربندی.

هنگام طراحی برای طرح‌بندی‌های تطبیقی، وضعیت رابط کاربری را در طول تغییرات پیکربندی مانند تغییر اندازه صفحه نمایش، تا کردن و تغییر جهت‌گیری حفظ کنید. معماری شما باید تأیید کند که وضعیت فعلی کاربر حفظ می‌شود و یک تجربه یکپارچه ارائه می‌دهد.

طراحی کامپوننت‌های رابط کاربری قابل استفاده مجدد و قابل ترکیب.

اجزای رابط کاربری بسازید که قابل استفاده مجدد و قابل ترکیب باشند تا از طراحی تطبیقی ​​پشتیبانی کنند. این به شما امکان می‌دهد اجزا را ترکیب و تنظیم مجدد کنید تا بدون نیاز به تغییر اساسی، با اندازه‌ها و حالت‌های مختلف صفحه نمایش سازگار شوند.

در نظر بگیرید که چگونه هر بخش از برنامه خود را به صورت جداگانه قابل آزمایش کنید.

یک API خوش‌تعریف برای واکشی داده‌ها از شبکه، آزمایش ماژولی که آن داده‌ها را در یک پایگاه داده محلی نگه می‌دارد، تسهیل می‌کند. اگر به جای آن، منطق این دو تابع را در یک مکان ترکیب کنید، یا کد شبکه خود را در کل پایگاه داده خود توزیع کنید، آزمایش بسیار دشوارتر، اگر نگوییم غیرممکن، می‌شود.

نوع‌ها مسئول سیاست همزمانی خود هستند.

اگر یک نوع، کار مسدودسازی طولانی‌مدت انجام می‌دهد، آن نوع باید مسئول انتقال آن محاسبه به نخ مناسب باشد. نوع، نوع محاسباتی که انجام می‌دهد و اینکه محاسبه باید در کدام نخ اجرا شود را می‌داند. انواع باید main-safe باشند، به این معنی که فراخوانی آنها از نخ اصلی بدون مسدود کردن آن، ایمن باشد.

تا حد امکان داده‌های مرتبط و جدید را حفظ کنید.

به این ترتیب، کاربران می‌توانند حتی زمانی که دستگاهشان در حالت آفلاین است، از عملکرد برنامه شما لذت ببرند. به یاد داشته باشید که همه کاربران شما از اتصال ثابت و پرسرعت لذت نمی‌برند و حتی اگر هم داشته باشند، ممکن است در مکان‌های شلوغ، آنتن‌دهی ضعیفی داشته باشند.

مزایای معماری

پیاده‌سازی یک معماری خوب در برنامه شما مزایای زیادی برای تیم‌های پروژه و مهندسی به همراه دارد:

  • قابلیت نگهداری، کیفیت و استحکام کلی برنامه را بهبود می‌بخشد.
  • به برنامه اجازه می‌دهد تا مقیاس‌پذیر باشد. افراد و تیم‌های بیشتری می‌توانند با حداقل تداخل کد، در یک کدبیس مشارکت کنند.
  • به روند جذب نیرو کمک می‌کند. از آنجایی که معماری، ثبات را به پروژه شما می‌آورد، اعضای جدید تیم می‌توانند به سرعت با شرایط جدید آشنا شوند و در زمان کمتری کارآمدتر باشند.
  • تست آسان‌تر. یک معماری خوب، انواع ساده‌تری را که عموماً تست آنها آسان‌تر است، تشویق می‌کند.
  • اشکالات را می‌توان به صورت روشمند و با فرآیندهای کاملاً تعریف‌شده بررسی کرد.

سرمایه‌گذاری در معماری همچنین تأثیر مستقیمی بر کاربران دارد. آن‌ها به دلیل یک تیم مهندسی پربارتر، از یک برنامه پایدارتر و ویژگی‌های بیشتر بهره‌مند می‌شوند. با این حال، معماری همچنین نیاز به یک سرمایه‌گذاری از قبل دارد. برای اینکه بتوانید این زمان را برای بقیه سازمان خود توجیه کنید، به این مطالعات موردی نگاهی بیندازید که در آن‌ها شرکت‌های دیگر داستان‌های موفقیت خود را در مورد داشتن یک معماری خوب در برنامه خود به اشتراک می‌گذارند.

نمونه‌ها

نمونه‌های زیر معماری خوب برنامه را نشان می‌دهند: