اصول SOLID برای توسعه دهندگان Android

اصول SOLID برای توسعه دهندگان Android
فهرست مقاله [نمایش]

    اساساً  SOLID یکی از مهمترین کلمات اختصاری در مفاهیم برنامه نویسی شی گرا است. استفاده از اصول SOLID در توسعه Android برای رعایت اصول clean code می تواند مفید و موثر باشد. بنابراین اگر توسعه دهندگان اندروید کدهای خود را بدون استفاده از اصول طراحی ساختار یافته مانند اصول SOLID طراحی و پیاده سازی کنند ، مشکلات طولانی مدت ایجاد می کنند و احتمال موفقیت یک برنامه در آینده کاهش می یابد. هدف این مقاله بحث در مورد اهمیت اصول SOLID برای توسعه دهندگان اندروید در طراحی و اجرای یک برنامه قوی است.

    SOLID چیست؟

    SOLID مخفف پنج اصل طراحی در توسعه نرم افزار شی گرا است که با هدف درک بهتر ، انعطاف پذیری و نگهداری بیشتر طرح های نرم افزاری انجام می شود. این پنج اصل توسط Robert C. Martin(عمو باب) شرح داده شده است. اصول SOLID به طور خلاصه به شرح زیر است:

    •  S -  Single Responsibility Principle (SRP)
    • Open/Closed Principle - O
    • Liskov’s Substitution Principle - L
    • Interface Segregation Principle -  I
    • Dependency Inversion Principle - D

    چرا باید از اصول SOLID در توسعه نرم افزار استفاده کنیم؟

    اگر توسعه دهندگان بدون استفاده از اصول طراحی ساختار یافته مانند اصول SOLID کدهای خود را طراحی و اجرا کنند ، مشکلات طولانی مدت دیگری را برای توسعه دهندگان ایجاد می کنند که می خواهند در آینده روی پروژه کار کنند. این مسائل معمولاً به SoftwareRot ارجاع می شوند.

    منظور از SoftwareRot چیست؟

    SoftwareRot یا Code Rot یا SoftwareErosion به تدریج باعث کاهش کیفیت نرم افزار در طول زمان یا کاهش پاسخگویی آن می شود که در نهایت منجر به نقص ، غیرقابل استفاده شدن نرم افزار و نیاز به ارتقا خواهد شد.

    برخی علائم برای شناسایی SoftwareRot

    1. Rigidity: تغییرات کوچک منجر به بازسازی کل نرم افزار می شود.
    2. Fragility: تغییر در یک component باعث خطا یا نقص در component دیگر می شود.
    3. Immobility: اگر از قسمتی از یک component در سیستم دیگری استفاده نشود ، این قسمت component ثابت(immobile)محسوب می شود. Immobility معمولاً به دلیل اتصال و وابستگی بین اجزای مختلف ایجاد می شود.
    4. Viscosity: هنگامی که اجرای آن دشوار است و اجرای آن نیز طولانی است.

    Single Responsibility Principle(معروف به SRP)

    اصل  Single Responsibility Principleنشان می دهد که هر کلاس باید یک و فقط یک مسئولیت داشته باشد. به عبارت دیگر اگر کلاس ما بیش از یک مسئولیت را انجام دهد  باید عملکردها و مسئولیت ها را در کلاس های مختلف تقسیم کنیم. بنابراین این باعث می شود کلاس قدرت بیشتری داشته باشد.

    به عنوان مثال در مثال زیر اگر کلاس User شامل ویژگی ها و رفتارهای کلاس UserType باشد ، طراحی خوبی برای این کلاس نخواهد بود زیرا در این وضعیت کلاس بیش از یک مسئولیت دارد. بنابراین اگر بخواهیم مسئولیتی را تغییر دهیم  احتمالاً باید مسئولیت های دیگر را نیز تغییر دهیم.

    open class User {
        var firstName: String? = null
        var lastName: String? = null
    }
    
    class UserType : User() {
        var selecting = 0
        val isSpecialUser: Boolean
            get() = selecting == 7
    }
    

     

     

    Open/Closed Principle

    در مفاهیم شی گرا اصل Open / Closed به این معنی است: "entity های نرم افزاری مانند کلاس ها,کامپوننت ها,ماژول ها باید برای توسعه باز باشند  اما برای ویرایش بسته هستند". یعنی چنین entity می تواند اجازه دهد رفتار آن بدون ویرایش source code خود توسعه یابد.

    احتمالاً به نظر می رسد که این دو قسمت از اصل Open / Closed متناقض است  اما اگر کلاس ها و وابستگی های آنها را به درستی و دقیق ساختار دهید می توانید بدون ویرایش source code موجود قابلیت های خود را اضافه کنید. به طور کلی ، شما با مراجعه به Abstraction در مفاهیم شی گرا برای وابستگی هایی مانند interface ها یا abstract class ها به این اصل دست می یابید نه استفاده از کلاس concrete.

    نکته: کلاس های concrete در برنامه نویسی شئ گرا کلاس هایی هستند که هیچ مفهوم انتزاعی (متد anstract یا عضو virtual)  در آنها وجود ندارد و تمام متد های آنها دارای پیاده سازی هستند و در نتیجه میتوان از آنها شی ساخت.

     علاوه بر این می توان با ایجاد کلاس جدیدی که interface ها را پیاده سازی می کند قابلیت ها را اضافه کرد. با استفاده از این روش می توانید خطر بروز اشکالات جدید در کد موجود را کاهش دهید و منجر به ساخت اپلیکیشن قوی تری میشود. به عنوان مثال می توان کلاسهای زیر را با استفاده از این اصل به کلاس دوم تغییر داد.

    class Triangle {
        var base = 0.0
        var height = 0.0
    }
    class Square {
        var side = 0.0
    }
    
    class AreaCalculate{
        fun getTriangleArea(triangle: Triangle): Double {
            return triangle.base * triangle.height / 2.also {it}
        }
        fun getSquareArea(square: Square): Double {
            return square.side * square.side
        }
    }
    

     

    پس از استفاده از این اصل:

     

    interface Shape {
        val area: Double
    }
    class Triangle : Shape {
        var base = 0.0
        var height = 0.0
        override val area: Double
            get() = base * height / 2
    }
    
    class Square : Shape {
        var side = 0.0
        override val area: Double
            get() = side * side
    }
    
    class AreaCalculate {
        fun getShapeArea(shape: Shape): Double {
            return shape.area
        }
    }
    
    

     

     

    Liskov’s Substitution Principle

    این اصل نشان می دهد که کلاسهای والد باید بدون تغییر رفتار نرم افزار به راحتی با کلاسهای فرزند خود جایگزین شوند. این بدان معنی است که یک زیر کلاس باید متدهای کلاس اصلی را نادیده بگیرد که این عملکرد کلاس والد را نمی شکند. این بدان معنی است که یک زیر کلاس باید متدهای کلاس اصلی را override کند که این عملکرد کلاس والد را نمی شکند. به عنوان مثال در مثال زیر هر Fragment می خواهد Interface را پیاده سازی کند. بنابراین شما باید الزامات و تغییرات در کلاس آنها را کنترل کنید. به طور خلاصه در اجرای اصلی ما هرگز نباید منطق را مدیریت کنیم.

    interface ClickListener {
            fun onClick()
        }
    
        class SampleFragment : ClickListener {
            override fun onClick() {
                decrementClickCount()
                //You should manage the logic here!
            }
    
            fun decrementClickCount() {}
        }
    
        class TestFragment : ClickListener {
            override fun onClick() {
                incrementClickCount()
                //You should manage the logic here!
            }
    
            fun incrementClickCount() {}
        }
    
        fun onButtonClick(clickListener: ClickListener) {
    //Handling the changes and requirements here would be a wrong place     and an incorrect solution!
            clickListener.onClick()
        }
    

     

     

    Interface Segregation Principle

    interface-segregation principle نشان می دهد کلاسهایی که interface ها را پیاده سازی می کنند مجبور نیستند متد هایی را که استفاده نمی کنند پیاده سازی کنند. این اصل به این واقعیت مربوط می شود که بسیاری از interface های خصوصی بهتر از یک interface عمومی هستند. علاوه بر این  این اولین اصل است که در interface استفاده می شود تمام اصول قبلی در کلاس ها اعمال می شود. به عنوان مثال اگر interface زیر را داشته باشیم client را مجبور به اجرای onLongClick می کند حتی اگر نیازی به فشار طولانی نداشته باشند. بنابراین می تواند منجر به overhead متدهای استفاده نشده شود. در یک کلام  برای نادیده گرفتن متدهای استفاده نشده با داشتن دو interface جداگانه می تواند مفید باشد.

    interface MyOnClickListener {
        fun onClick(v: View?)
        fun onLongClick(v: View?): Boolean
        fun onTouch(v: View?, event: MotionEvent?)
    }
    

     

    پس از استفاده از این اصل:

    interface OnClickListener {
        fun onClick(v: View?)
    }
    
    interface OnLongClickListener {
        fun onLongClick(v: View?): Boolean
    }
    
    interface OnTouchListener {
        fun onTouch(v: View?, event: MotionEvent?)
    }
    

     

     

    Dependency Inversion Principle

    این اصل نشان می دهد که ماژول های سطح بالا نباید به سطح پایین بستگی داشته باشند. بنابراین  هر دو باید به abstraction بستگی داشته باشند. علاوه بر این ، abstraction نباید به جزئیات وابستگی داشته باشد. جزئیات باید به abstraction وابستگی داشته باشد.

    در یک کلام ، کلاس ها باید به  abstraction وابستگی داشته باشد  اما به concretion وابستگی ندارد. به عنوان مثال در  زیر یک لایه abstraction جدید از طریق اینترفیس Engine برای از بین بردن وابستگی بین دو کلاس اضافه شده است.

    class Engine
    
    class ToyCar internal constructor() {
        var engine: Engine? = null
    
        init {
            Engine = Engine()
        }
    }
    

     

    پس از استفاده از این اصل:

     

    interface IEngine
    
    class Engine : IEngine {
        var capacity = 0.0
    }
    
    class ToyCar internal constructor(var iEngine: IEngine)
    

     

    در اینجا می توان به مثال دیگری در Android اشاره کرد این است که اگر ما می خواهیم الگوی طراحی MVP را پیاده سازی کنیم ، باید Presenter را در View خود نگه داریم. در نتیجه ، اگر Presenter   را در View نگه داریم باعث اتصال فشرده می شود. اساساً ما باید یک interface برای جدا کردن در این شرایط ایجاد کنیم. علاوه بر این ، برخی از کتابخانه های معروف برای پیاده سازی این اصل در Android مانندDagger2  وجود دارد. در نتیجه استفاده از اصول SOLID در توسعه اندروید برای پیروی از اصول clean code می تواند مفید باشد. هدف این مقاله بحث در مورد اهمیت اصول SOLID برای توسعه دهندگان اندروید است.

    سوالات خود را در قسمت کامنت ها مطرح کنید تا به آنها پاسخ بدهم.

     

     

    اگر می خواهید با اصول Solid به صورت مفصل تر آشنا بشوید دوره  رایگان  آموزش اصول Solid در سی شارپ  را مشاهده نمایید

     


    • نویسنده: میثم بابائی

    ارسال دیدگاه

    برای افزودن دیدگاه خود، نیاز است ابتدا وارد حساب کاربری‌تان شوید


    دیدگاه کاربران

    avatar
    علی
    1399/06/25

    بسیار عالی

    تشکر از مطالب مفیدتون