JavaRush /Java Blogu /Random-AZ /Kodunuzu daha təmiz edəcək SOLID prinsipləri
Paul Soia
Səviyyə
Kiyv

Kodunuzu daha təmiz edəcək SOLID prinsipləri

Qrupda dərc edilmişdir
SOLID nədir? SOLID abbreviaturasının mənası budur: - S: Tək  Məsuliyyət Prinsipi. - O: Açıq-Qapalı Prinsip  . - L: Liskov əvəzetmə prinsipi  . - I: İnterfeys Seqreqasiya Prinsipi  . - D: Asılılığın inversiya prinsipi  . SOLID prinsipləri modulları necə dizayn etməyi məsləhət görür, yəni. tətbiqin qurulduğu kərpiclər. Prinsiplərin məqsədi aşağıdakı modulları tərtib etməkdir: - dəyişikliyi təşviq edən - başa düşmək asandır - təkrar istifadə edilə bilən. Beləliklə, nümunələrlə onların nə olduğuna baxaq.
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())
        }
    }
}
Bu nümunədə nə görürük? FirebaseAuth konstruktorda ötürülür və biz ondan daxil olmaq üçün istifadə edirik. Cavabda bir səhv alsaq, mesajı fayla yazırıq. İndi gəlin görək bu kodda nə səhvdir. 1. S: Vahid Məsuliyyət Prinsip  : Bir sinif (və ya funksiya/metod) yalnız bir şeyə görə məsuliyyət daşımalıdır. Əgər sinif bir neçə məsələnin həllinə cavabdehdirsə, onun bu problemlərin həllini həyata keçirən alt sistemləri bir-birinə bağlıdır. Belə bir alt sistemdəki dəyişikliklər digərində dəyişikliklərə səbəb olur. Bizim nümunəmizdə loginUser funksiyası iki şeyə cavabdehdir - giriş və fayla xətanın yazılması. Bir məsuliyyətin olması üçün səhvin qeydə alınması məntiqi ayrıca sinifdə yerləşdirilməlidir.
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())
        }
    }
}
Səhv qeydinin məntiqi ayrı bir sinifə köçürüldü və indi loginUser metodunun yalnız bir məsuliyyəti var - avtorizasiya. Əgər xəta qeydinin məntiqini dəyişdirmək lazımdırsa, biz bunu loginUser 2 funksiyasında deyil, FileLogger sinfində edəcəyik  . lakin dəyişiklik üçün deyil. Bu kontekstdə, genişləndirməyə açıqlıq, ehtiyac yaranarsa, sinifə, modula və ya funksiyaya yeni davranış əlavə etmək qabiliyyətidir və dəyişməyə qapalılıq proqram təminatı obyektlərinin mənbə kodunun dəyişdirilməsinə qadağadır. İlk baxışdan bu, mürəkkəb və ziddiyyətli səslənir. Amma baxsanız, prinsip kifayət qədər məntiqlidir. OCP prinsipinə əsasən proqram təminatı mövcud kodu dəyişdirməklə deyil, yeni kod əlavə etməklə dəyişdirilir. Yəni, ilkin yaradılmış kod “toxunulmamış” və sabit olaraq qalır və yeni funksionallıq ya icra mirası, ya da abstrakt interfeyslərdən və polimorfizmdən istifadə yolu ilə təqdim olunur. Beləliklə, FileLogger sinfinə baxaq. Əgər logları başqa fayla yazmaq lazımdırsa, adı dəyişə bilərik:
class FileLogger{
    fun logError(error: String) {
        val file = File("errors2.txt")
        file.appendText(text = error)
    }
}
Ancaq sonra bütün qeydlər yeni bir fayla yazılacaq, çox güman ki, bizə lazım deyil. Bəs sinifin özünü dəyişmədən loggerimizi necə dəyişə bilərik?
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)
    }

}
Biz FileLogger sinfini və logError funksiyasını açıq kimi qeyd edirik, yeni CustomErrorFileLogger sinfi yaradırıq və yeni giriş tətbiqi yazırıq. Nəticədə, bizim sinif funksionallığı genişləndirmək üçün əlçatandır, lakin dəyişiklik üçün bağlıdır. 3. L: Liskov əvəzetmə prinsipi  . Alt siniflərin öz supersiniflərini əvəz edə bilməsi lazımdır. Bu prinsipin məqsədi ondan ibarətdir ki, nəsil siniflər proqramı pozmadan törəmə siniflərin yerinə istifadə oluna bilər. Əgər kodun bir sinfin növünü yoxladığı ortaya çıxarsa, əvəzetmə prinsipi pozulur. Xələf sinfini belə yazsaq:
class CustomErrorFileLogger : FileLogger() {

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

}
İndi gəlin FileLogger sinfini depoda CustomErrorFileLogger ilə əvəz edək.
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())
        }
    }

}
Bu halda, logError əsas sinifdən çağırılacaq və ya siz zəngi fileLogger.logError(e.message.toString()) ilə fileLogger.customErrorLog(e.message.toString()) ilə dəyişməli olacaqsınız 4. I :  İnterfeys Ayrılma Prinsipi Müəyyən bir müştəri üçün nəzərdə tutulmuş yüksək ixtisaslaşmış interfeyslər yaradın. Müştərilər istifadə etmədikləri interfeyslərdən asılı olmamalıdırlar. Bu mürəkkəb səslənir, amma əslində çox sadədir. Məsələn, FileLogger sinifini interfeys edək, lakin ona daha bir funksiya əlavə edək:
interface FileLogger{

    fun printLogs()

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

}
İndi bütün nəsillərdən printLogs funksiyasını həyata keçirmək tələb olunacaq, hətta bütün nəsil siniflərində buna ehtiyacımız olmasa belə.
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

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

}
və indi boş funksiyalarımız olacaq, bu kod təmizliyi üçün pisdir. Bunun əvəzinə biz interfeysdə standart dəyər yarada və sonra funksiyanı yalnız ehtiyac duyulan siniflərdə ləğv edə bilərik:
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)
    }

}
İndi FileLogger interfeysini tətbiq edəcək siniflər daha təmiz olacaq. 5. D: Asılılığın inversiya prinsipi  . Asılılığın obyekti konkret bir şey deyil, abstraksiya olmalıdır. Əsas dərsimizə qayıdaq:
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())
        }
    }

}
Deyəsən, biz burada hər şeyi artıq konfiqurasiya edib düzəltmişik. Ancaq dəyişdirilməli olan daha bir məqam var. Bu, FirebaseAuth sinifindən istifadə edir. Nə vaxtsa avtorizasiyanı dəyişməli və Firebase vasitəsilə deyil, məsələn, bir növ API sorğusundan istifadə etməklə daxil olmalıyıqsa nə olacaq? Onda biz çox şeyi dəyişməli olacağıq və bunu istəməzdik. Bunun üçün signInWithEmailAndPassword(email: String, password: String) funksiyası ilə interfeys yaradın:
interface Authenticator{
    fun signInWithEmailAndPassword(email: String, password: String)
}
Bu interfeys bizim abstraksiyamızdır. İndi biz girişin xüsusi tətbiqlərini edirik
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) {
        //другой способ логина
    }

}
Və “MainRepository” sinfində indi konkret icradan asılılıq yoxdur, yalnız abstraksiyadan
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())
        }
    }

}
İndi isə avtorizasiya metodunu dəyişmək üçün modul sinifində yalnız bir sətri dəyişmək lazımdır.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION