JavaRush /وبلاگ جاوا /Random-FA /اصول جامد که کد شما را تمیزتر می کند
Paul Soia
مرحله
Kiyv

اصول جامد که کد شما را تمیزتر می کند

در گروه منتشر شد
SOLID چیست؟ در اینجا مخفف SOLID مخفف آن است: - S: Single  Responsibility Principle. - O: اصل باز-بسته  . - L: اصل جایگزینی لیسکوف  . - I: اصل جداسازی رابط  . - د: اصل وارونگی وابستگی  . اصول SOLID نحوه طراحی ماژول ها را توصیه می کند. آجرهایی که برنامه از آنها ساخته شده است. هدف از این اصول طراحی ماژول هایی است که: - تغییر را ترویج می کنند - قابل درک آسان هستند - قابل استفاده مجدد هستند بنابراین، بیایید با مثال هایی به چیستی آنها نگاه کنیم.
class MainRepository(
    private val auth: FirebaseAuth
) {
    suspend fun loginUser(email: String, password: String) {
        try {
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            val file = File("errors.txt")
            file.appendText(text = e.message.toString())
        }
    }
}
در این مثال چه می بینیم؟ FirebaseAuth در سازنده ارسال می شود و ما از آن برای ورود به سیستم استفاده می کنیم. اگر در پاسخ با خطایی مواجه شدیم، پیام را در یک فایل می نویسیم. حالا بیایید ببینیم این کد چه مشکلی دارد. 1. S: اصل مسئولیت منفرد  : یک کلاس (یا تابع/روش) باید تنها مسئول یک چیز باشد. اگر یک کلاس مسئول حل چندین مسئله باشد، زیرسیستم های آن که حل این مسائل را پیاده سازی می کنند به یکدیگر متصل می شوند. تغییرات در یکی از این زیرسیستم ها منجر به تغییرات در زیرسیستم دیگری می شود. در مثال ما، تابع loginUser مسئول دو چیز است - ورود به سیستم و نوشتن یک خطا در یک فایل. برای اینکه یک مسئولیت وجود داشته باشد، منطق ثبت خطا باید در یک کلاس جداگانه قرار گیرد.
class FileLogger{
    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }
}
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: FileLogger,
) {
    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }
}
منطق ثبت خطا به یک کلاس جداگانه منتقل شده است و اکنون روش loginUser تنها یک مسئولیت دارد - مجوز. اگر منطق ثبت خطا نیاز به تغییر داشته باشد، این کار را در کلاس FileLogger انجام خواهیم داد، نه در تابع loginUser 2. O: Open-Closed  Principle: موجودیت های نرم افزار (کلاس ها، ماژول ها، توابع) باید برای پسوند باز باشند. اما نه برای اصلاح در این زمینه، باز بودن افزونه، توانایی افزودن رفتار جدید به کلاس، ماژول یا تابع در صورت نیاز است و بسته بودن تغییر، ممنوعیت تغییر کد منبع موجودیت‌های نرم‌افزار است. در نگاه اول، این موضوع پیچیده و متناقض به نظر می رسد. اما اگر به آن نگاه کنید، اصل کاملاً منطقی است. پیروی از اصل OCP این است که نرم افزار نه با تغییر کد موجود، بلکه با افزودن کد جدید تغییر می کند. یعنی کد ایجاد شده اولیه "دست نخورده" و پایدار می ماند و عملکرد جدید یا از طریق وراثت پیاده سازی یا از طریق استفاده از رابط های انتزاعی و چند شکلی معرفی می شود. بنابراین بیایید به کلاس FileLogger نگاه کنیم. اگر نیاز به نوشتن گزارش در فایل دیگری داریم، می‌توانیم نام آن را تغییر دهیم:
class FileLogger{
    fun logError(error: String) {
        val file = File("errors2.txt")
        file.appendText(text = error)
    }
}
اما پس از آن همه گزارش ها در یک فایل جدید نوشته می شوند که به احتمال زیاد به آن نیازی نداریم. اما چگونه می توانیم لاگر خود را بدون تغییر خود کلاس تغییر دهیم؟
open class FileLogger{

    open fun logError(error: String) {
        val file = File("error.txt")
        file.appendText(text = error)
    }

}

class CustomErrorFileLogger : FileLogger() {

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
کلاس FileLogger و تابع logError را به عنوان باز علامت گذاری می کنیم، یک کلاس CustomErrorFileLogger جدید ایجاد می کنیم و یک پیاده سازی لاگ جدید می نویسیم. در نتیجه، کلاس ما برای گسترش عملکرد در دسترس است، اما برای اصلاح بسته است. 3. L: اصل جایگزینی لیسکوف  . لازم است که زیر کلاس ها بتوانند به عنوان جایگزینی برای سوپر کلاس های خود عمل کنند. هدف از این اصل این است که کلاس‌های descendant را می‌توان به جای کلاس‌های والد که از آنها مشتق شده‌اند، بدون شکستن برنامه استفاده کرد. اگر معلوم شد که کد نوع کلاس را بررسی می کند، اصل جایگزینی نقض می شود. اگر کلاس جانشین را اینگونه بنویسیم:
class CustomErrorFileLogger : FileLogger() {

    fun customErrorLog(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
و حالا بیایید کلاس FileLogger را با CustomErrorFileLogger در مخزن جایگزین کنیم
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: CustomErrorFileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        }catch(e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
در این حالت، logError از کلاس والد فراخوانی می شود، یا باید تماس را به fileLogger.logError(e.message.toString()) به fileLogger.customErrorLog(e.message.toString()) تغییر دهید. : اصل جداسازی رابط. ایجاد رابط های بسیار تخصصی که برای یک مشتری خاص طراحی شده است. کلاینت ها نباید به رابط هایی که استفاده نمی کنند وابسته باشند. پیچیده به نظر می رسد، اما در واقع بسیار ساده است. به عنوان مثال، بیایید کلاس FileLogger را به یک رابط تبدیل کنیم، اما یک تابع دیگر به آن اضافه کنیم:
interface FileLogger{

    fun printLogs()

    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }

}
اکنون همه فرزندان باید تابع printLogs را پیاده‌سازی کنند، حتی اگر در همه کلاس‌های decendant به آن نیاز نداشته باشیم.
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
و حالا توابع خالی خواهیم داشت که برای پاکی کد بد است. در عوض، می‌توانیم یک مقدار پیش‌فرض در اینترفیس ایجاد کنیم و سپس تابع را فقط در کلاس‌هایی که در آن‌ها مورد نیاز است لغو کنیم:
interface FileLogger{

    fun printLogs() {
        //Howая-то дефолтная реализация
    }

    fun logError(error: String) {
        val file = File("errors.txt")
        file.appendText(text = error)
    }

}
class CustomErrorFileLogger : FileLogger{

    override fun logError(error: String) {
        val file = File("my_custom_error_file.txt")
        file.appendText(text = error)
    }

}
اکنون کلاس هایی که رابط FileLogger را پیاده سازی می کنند تمیزتر خواهند بود. 5. د: اصل وارونگی وابستگی  . موضوع وابستگی باید یک انتزاع باشد، نه چیزی عینی. بیایید به کلاس اصلی خود برگردیم:
class MainRepository(
    private val auth: FirebaseAuth,
    private val fileLogger: FileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        } catch (e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
به نظر می رسد که ما قبلاً همه چیز را در اینجا پیکربندی و رفع کرده ایم. اما هنوز یک نکته دیگر وجود دارد که باید تغییر کند. این از کلاس FirebaseAuth استفاده می کند. اگر در مقطعی نیاز به تغییر مجوز و ورود به سیستم نه از طریق Firebase، بلکه مثلاً با استفاده از نوعی درخواست API داشته باشیم، چه اتفاقی خواهد افتاد؟ سپس ما باید چیزهای زیادی را تغییر دهیم، و ما این را نمی خواهیم. برای انجام این کار، یک رابط با تابع signInWithEmailAndPassword (ایمیل: رشته، رمز عبور: رشته):
interface Authenticator{
    fun signInWithEmailAndPassword(email: String, password: String)
}
این رابط انتزاعی ماست. و اکنون پیاده سازی های خاصی از ورود انجام می دهیم
class FirebaseAuthenticator : Authenticator{

    override fun signInWithEmailAndPassword(email: String, password: String) {
        FirebaseAuth.getInstance().signInWithEmailAndPassword(email, password)
    }

}
class CustomApiAuthenticator : Authenticator{

    override fun signInWithEmailAndPassword(email: String, password: String) {
        //другой способ логина
    }

}
و در کلاس "MainRepository" اکنون هیچ وابستگی به پیاده سازی خاص وجود ندارد، بلکه فقط به انتزاع بستگی دارد.
class MainRepository (
    private val auth: Authenticator,
    private val fileLogger: FileLogger,
) {

    suspend fun loginUser(email: String, password: String) {
        try{
            auth.signInWithEmailAndPassword(email, password)
        }catch(e: Exception) {
            fileLogger.logError(e.message.toString())
        }
    }

}
و اکنون برای تغییر روش مجوز، باید تنها یک خط را در کلاس ماژول تغییر دهیم.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION