“ما را در اینستاگرام دنبال کنید”
در این مقاله از سری مقالات برنامه نویسی اندروید اومدیم در مورد اصول SOLID در اندوید، صحبت کنیم. پس با سایت ترولرن همراه باش.
“قبل از شروع مقاله، بگم که بعد از مطالعه این مطلب، از آموزش پروژه محور برنامه نویسی اندروید سایتمون یعنی دوره ژنرال اندروید غافل نشید.”
توی دوره ژنرال صفر تا صد اصول SOLID در اندوید رو در یک فصل کامل توضیح دادیم و همچنین توی پروژه های دیجی کالا و اسنپ فود به صورت عملی و پروژه محور از اصول SOLID استفاده کردیم و بطور کامل اون رو آموزش دادیم.
اصول SOLID در اندوید یک مجموعه اصول طراحی نرمافزاری هستند که بهبود قابلیت توسعه، قابلیت نگهداری و قابلیت تست کد را در برنامههای نرمافزاری ارتقا میدهند. اصول SOLID ابتکار شدهاند تا کد قابل تغییر، قابلیت خوانا بودن و بازدهی بالا را داشته
باشد. در زیر به اصول SOLID در مورد توسعه برنامههای اندروید اشاره خواهم کرد:
این اصل میگوید که هر کلاس یا ماژول باید فقط یک مسئولیت مشخص را داشته باشد. در اندروید، این اصل نشان میدهد که هر کلاس باید فقط برای انجام یک کار خاص طراحی شود، مانند کلاسهایی برای نمایش دادهها، پردازش منطقی و ارتباط با پایگاه داده.
این اصل میگوید که باید کلاسها و ماژولها برای توسعه باز بوده ولی بسته برای تغییرات باشند. در اندروید، میتوانید از این اصل استفاده کنید تا با استفاده از ارثبری یا رابطها، بخشهای قابل توسعه و قابل تغییر را تعریف کنید.
این اصل میگوید که باید بتوانید یک شیء را با شیء جایگزین کنید بدون تغییر رفتار کد. در اندروید، این اصل بیان میکند که کلاسها باید قادر باشند جایگزین یکدیگر شوند بدون ایجاد مشکلات در کارکرد برنامه.
این اصل میگوید که کلاسها نباید به فانکشن که استفاده نمیکنند وابسته باشند. در اندروید، باید رابطها به گونهای طراحی شوند که فقط متدهای مورد نیاز کلاسها را در بر داشته باشند.
این اصل میگوید که برنامهها باید به وابستگیها از طریق تزریق وابستگیها (Dependency Injection) نگاه کنند. به عبارت دیگر، برنامهها باید به یک رابط عمل کنند و نه به یک پیادهسازی خاص. این اصل به شما کمک میکند تا وابستگیهای خود را با استفاده از
تزریق وابستگیها مدیریت کنید و امکان تعویض و بهبود قابلیت تست کد را فراهم کنید.
این اصول SOLID برای طراحی و توسعه بهتر برنامههای اندروید بسیار مفید هستند. با رعایت این اصول، میتوانید کدی با قابلیت تغییر و توسعه بالا، خوانایی بهتر و قابلیت آزمون بهتر ایجاد کنید. همچنین، استفاده از الگوها و روشهای طراحی مناسب نیز به شما
کمک میکند تا این اصول را به خوبی پیادهسازی کنید.
اصل مسئولیتهای مجزا (Single Responsibility Principle – SRP) میگوید که یک کلاس باید فقط یک مسئولیت یا وظیفه را بر عهده داشته باشد و تغییر در یک مسئولیت، باید تغییر کوچکی در کد داشته باشد.
برای نمایش این اصل، میتوانیم یک مثال ساده را در نظر بگیریم. فرض کنید که ما یک برنامهی مدیریت کاربران داریم که قادر است کاربران را ثبت نام کند و اطلاعات آنها را نمایش دهد. برای این منظور، میتوانیم از اصل SRP استفاده کنیم.
class User(val name: String, val email: String) { fun register() { // عملیات ثبت نام } fun displayUserInfo() { // نمایش اطلاعات کاربر } }
در این مثال، کلاس User دو متد را ارائه میدهد. متد register برای انجام عملیات ثبت نام کاربر و استفاده از اطلاعات موجود در کلاس User استفاده میشود. متد displayUserInfo نیز برای نمایش اطلاعات کاربر استفاده میشود. در اینجا، هر دو متد مرتبط به
مفهوم یک کاربر هستند و هر کدام یک مسئولیت خاص را بر عهده دارند. متد register مسئولیت ثبت نام را بر عهده دارد و متد displayUserInfo مسئولیت نمایش اطلاعات کاربر را بر عهده دارد. با این رویکرد، هر تغییر در یکی از مسئولیتها، تنها تغییراتی در کد
مربوط به آن مسئولیت را به دنبال دارد و تغییر در یک مسئولیت تأثیری بر دیگری نخواهد داشت.
با استفاده از اصل SRP، کد بهبود یافته و قابلیت توسعه و تستپذیری بیشتری خواهد داشت. همچنین، تغییر در یک مسئولیت باعث ایجاد خطاهای کمتری در سایر بخشهای کد خواهد شد.
اصل باز بسته (Open/Closed Principle – OCP) میگوید که باید کلاسها برای توسعه باز باشند، اما بسته برای تغییر. به این معنی که باید بتوانیم عملکرد یک کلاس را با اضافه کردن کدهای جدید یا ارثبری از آن تغییر دهیم، بدون اینکه کد موجود را تغییر دهیم.
یک مثال ساده از اصل OCP را در نظر بگیرید. فرض کنید که ما یک برنامه برای محاسبه قیمت محصولات داریم و قرار است قوانین محاسبه قیمت براساس نوع محصول تغییر کنند. اصل OCP میتواند به ما کمک کند تا برنامه را به گونهای طراحی کنیم که بتوانیم
قوانین محاسبه قیمت را تغییر دهیم بدون تغییر در کد موجود.
interface Product { fun calculatePrice(): Double } class Book : Product { override fun calculatePrice(): Double { // قوانین محاسبه قیمت کتاب } } class Electronic : Product { override fun calculatePrice(): Double { // قوانین محاسبه قیمت محصول الکترونیکی } } class PriceCalculator(private val products: List<Product>) { fun calculateTotalPrice(): Double { var totalPrice = 0.0 for (product in products) { totalPrice += product.calculatePrice() } return totalPrice } }
در این مثال، ما یک رابط به نام Product ایجاد کردهایم که تعریف میکند که هر محصول باید قادر به محاسبه قیمت خود باشد. سپس دو کلاس Book و Electronic را ایجاد کردهایم که از رابط Product ارثبری میکنند و قوانین محاسبه قیمت خود را پیادهسازی
میکنند.
سپس ما یک کلاس به نام PriceCalculator ایجاد کردهایم که یک لیست از محصولات را به عنوان ورودی میگیرد و مجموع قیمت همه محصولات را محاسبه میکند. این کلاس با استفاده از رابط Product به عنوان ورودی، به صورت باز برای توسعه است و میتوانیم
به سادگی محصولات جدیدی را به آن اضافه کنیم و قوانین محاسبه قیمت آنها را پیادهسازی کنیم، بدون اینکه کد PriceCalculator را تغییر دهیم.
با استفاده از اصل OCP، برنامه قابلیت توسعه و تغییر بیشتری خواهد داشت و ما میتوانیم قوانین محاسبه قیمت را با اضافه کردن محصولات جدید یا ارثبری از رابط Product تغییر دهیم، بدون تغییر در کد قبلی.
اصل جانشینی لیسکوف (Liskov Substitution Principle – LSP) میگوید که باید بتوانیم یک کلاس مشتق (Subclass) را به جای کلاس پایه (Base class) در هر مکانی استفاده کنیم، بدون اینکه عملکرد برنامه تغییر کند یا خطا رخ دهد.
برای نمایش این اصل، میتوانیم یک مثال ساده را در نظر بگیریم. فرض کنید که ما یک برنامه برای محاسبه مساحت شکلها داریم و قرار است از اصل LSP استفاده کنیم.
open class Shape { open fun calculateArea(): Double { return 0.0 } } class Rectangle(private val width: Double, private val height: Double) : Shape() { override fun calculateArea(): Double { return width * height } } class Circle(private val radius: Double) : Shape() { override fun calculateArea(): Double { return Math.PI * radius * radius } } fun printArea(shape: Shape) { val area = shape.calculateArea() println("The area is: $area") }
در این مثال، ما یک کلاس پایه به نام Shape ایجاد کردهایم که یک متد به نام calculateArea را پیادهسازی میکند تا محاسبه مساحت شکلها را انجام دهد. همچنین، متد calculateArea به صورت پیشفرض مقدار 0.0 را برمیگرداند.
سپس، دو کلاس Rectangle و Circle را ایجاد کردهایم که از کلاس Shape ارثبری میکنند و متد calculateArea را با توجه به نوع شکل خود، پیادهسازی میکنند.
در نهایت، ما یک تابع به نام printArea ایجاد کردهایم که یک شیء از نوع Shape را به عنوان ورودی میگیرد و مساحت آن را محاسبه و چاپ میکند.
با استفاده از اصل LSP، میتوانیم در هر جایی که از کلاس Shape استفاده میکنیم، شیءهایی از نوع Rectangle و Circle را جایگزین کنیم، بدون اینکه عملکرد برنامه تغییر کند. به عبارت دیگر، شیءهای مشتق باید قابل جایگزینی باشند و عملکرد همانند کلاس
پایه را رعایت کنند.
با استفاده از اصل LSP، برنامه قابلیت توسعه و تغییر بیشتری خواهد داشت و ما میتوانیم شکلهای جدیدی را اضافه کنیم که از کلاس Shape ارثبری کنند و متد calculateArea را با توجه به نوع شکل خود پیادهسازی کنند، بدون اینکه کد قبلی را تغییر دهیم.
اصل جداسازی رابط (Interface Segregation Principle – ISP) میگوید که باید رابطها را به گونهای طراحی کنیم که کلاسها فقط از فانکشن که نیاز دارند استفاده کنند و فانکشن اضافی را نداشته باشند.
برای نمایش این اصل، میتوانیم یک مثال ساده را در نظر بگیریم. فرض کنید که ما یک سیستم برای پخش موسیقی داریم و قرار است از اصل ISP استفاده کنیم.
interface MusicPlayer { fun playMusic() fun pauseMusic() fun stopMusic() fun nextTrack() fun previousTrack() fun shufflePlaylist() fun addToPlaylist() fun removeFromPlaylist() } class SimpleMusicPlayer : MusicPlayer { override fun playMusic() { // پخش موسیقی } override fun pauseMusic() { // مکث موسیقی } override fun stopMusic() { // توقف پخش موسیقی } override fun nextTrack() { // پخش قطعه بعدی } override fun previousTrack() { // پخش قطعه قبلی } override fun shufflePlaylist() { // تصادفی کردن لیست پخش } override fun addToPlaylist() { // افزودن قطعه به لیست پخش } override fun removeFromPlaylist() { // حذف قطعه از لیست پخش } } class MinimalMusicPlayer : MusicPlayer { override fun playMusic() { // پخش موسیقی } override fun pauseMusic() { // مکث موسیقی } override fun stopMusic() { // توقف پخش موسیقی } override fun nextTrack() { // پخش قطعه بعدی } override fun previousTrack() { // پخش قطعه قبلی } }
در این مثال، ما یک رابط به نام MusicPlayer ایجاد کردهایم که تمام عملیات مربوط به پخش موسیقی را تعریف میکند. اما این رابط بسیار کلی و حاوی فانکشن زیادی است که همه کلاسها نیازی به استفاده از آنها ندارند.
سپس، دو کلاس SimpleMusicPlayer و MinimalMusicPlayer را ایجاد کردهایم که هر دو از رابط MusicPlayer ارثبری میکنند و فانکشن مورد نیاز خود را پیادهسازی میکنند.
کلاس SimpleMusicPlayer تمام فانکشن رابط MusicPlayer را پیادهسازی میکند و در نتیجه میتواند تمام عملیات پخش موسیقی را انجام دهد.
اما کلاس MinimalMusicPlayer فقط فانکشن مورد نیاز برای پخش موسیقی را پیادهسازی میکند و فانکشن اضافی مانند shufflePlaylist، addToPlaylist و removeFromPlaylist را ندارد.
با استفاده از اصل ISP، رابط MusicPlayer به صورت جداگانه طراحی شده است، به طوری که کلاسMinimalMusicPlayer فقط از فانکشن مورد نیاز خود استفاده میکند و فانکشن اضافی را ندارد. این باعث میشود که کلاس MinimalMusicPlayer برای استفاده
در سیستمهایی که نیاز به عملکرد سادهتری دارند، مناسب باشد.
به این ترتیب، با استفاده از اصل ISP، رابطها را به گونهای طراحی میکنیم که هر کلاس فقط از فانکشن لازم برای خود استفاده کند و فانکشن اضافی را نداشته باشد. این کار باعث جدا بودن مسئولیتها و کاهش وابستگیها در سیستم میشود و قابلیت توسعه و تغییر بیشتری را فراهم میکند.
اصل وابستگی برای تهیهکننده (Dependency Inversion Principle – DIP) میگوید که باید برنامهها به اصطلاح برنامه به تهیهکننده (Program to an interface, not an implementation) بنویسیم، به این معنی که باید برنامهها به واسطه رابطها (Interface) با
تهیهکنندهها (Provider) ارتباط برقرار کنند نه با پیادهسازیها (Implementation) مستقیم.
برای نمایش این اصل، میتوانیم یک مثال ساده را در نظر بگیریم. فرض کنید که ما یک برنامه ساده ایجاد میکنیم که قادر است پیامهای خوش آمدگویی را نمایش دهد.
interface MessageProvider { fun getMessage(): String } class WelcomeMessageProvider : MessageProvider { override fun getMessage(): String { return "Welcome to our application!" } } class GreetingService(private val messageProvider: MessageProvider) { fun displayGreeting() { val message = messageProvider.getMessage() println(message) } }
در این مثال، ما یک رابط به نام MessageProvider ایجاد کردهایم که تابع getMessage را تعریف میکند. سپس یک کلاس به نام WelcomeMessageProvider را ایجاد کردهایم که این رابط را پیادهسازی میکند و پیام خوش آمدگویی را برمیگرداند.
سپس، یک کلاس به نام GreetingService را ایجاد کردهایم که یک نمونه از MessageProvider را به عنوان ورودی دریافت میکند و تابع displayGreeting را دارد. این تابع پیام خوش آمدگویی را از تهیهکننده (Provider) دریافت کرده و نمایش میدهد.
با استفاده از اصل DIP، برنامهها به رابط MessageProvider وابسته هستند نه به پیادهسازی خاص WelcomeMessageProvider. این به معنی این است که ما میتوانیم در آینده پیادهسازی دیگری از MessageProvider را ایجاد کنیم (مثلاً بازخوردیترین پیام
خوش آمدگویی) و برنامه هیچ تغییری نیاز ندارد. کافی است پیادهسازی جدید را ایجاد کرده و با استفاده از رابط MessageProvider در کلاس GreetingService استفاده کنیم.
به این ترتیب، با استفاده از اصل DIP، برنامهها به رابطها وابسته هستند نه به پیادهسازیها. این کمک میکند که ما بتوانیم پیادهسازیها را به راحتی تعویض کنیم و برنامههای قابل توسعه و قابل استفاده مجدد ایجاد کنیم.
و همچنین ممنون میشم از طریق ستارههای این پایین به این مقاله امتیاز بدی و اگه هر سوالی داشتی توی قسمت دیدگاه بپرس و قطعا بهت پاسخ میدیم.
دوره آموزشی کاتلین پیشرفته میتواند مهارتهای شما را با بهرهگیری از ابزارها و فناوریهای مدرن…
مزیتهای کاتلین نسبت به سایر زبانهای برنامه نویسی اندروید این است که سایر زبانها، از…
بهینهسازی عملکرد اپلیکیشنهای اندروید یکی از مهمترین فاکتورهایی است که برای کاربران در دنیای امروز…
مصاحبهی استخدامی کاتلین یک فرصت برای ارزیابی مهارتها و تواناییهای یک برنامهنویس در توسعه اپلیکیشنهای…
مصاحبه استخدام زبان کاتلین یک فرصت برای ارزیابی مهارتها و تواناییهای یک برنامهنویس در توسعه…
مصاحبه استخدام کاتلین یک فرصت برای ارزیابی مهارتها و تواناییهای یک برنامهنویس در توسعه اپلیکیشنهای…
View Comments