JavaRush /Java blogi /Random-UZ /Kodingizni tozalaydigan SOLID tamoyillari
Paul Soia
Daraja
Kiyv

Kodingizni tozalaydigan SOLID tamoyillari

Guruhda nashr etilgan
SOLID nima? Bu erda SOLID qisqartmasi nimani anglatadi: - S: Yagona  javobgarlik printsipi. - O: ochiq-yopiq printsip  . - L: Liskov almashtirish printsipi  . - I: Interfeysni ajratish printsipi  . - D: Bog'liqlik inversiyasi printsipi  . SOLID tamoyillari modullarni qanday loyihalashni maslahat beradi, ya'ni. ilova qurilgan g'ishtlar. Printsiplarning maqsadi quyidagilardan iborat bo'lgan modullarni loyihalash: - o'zgarishlarni rag'batlantirish - tushunish oson - qayta foydalanish mumkin Shunday qilib, keling, ularning nima ekanligini misollar bilan ko'rib chiqamiz.
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())
        }
    }
}
Ushbu misolda biz nimani ko'rmoqdamiz? FirebaseAuth konstruktorda uzatiladi va biz undan tizimga kirish uchun foydalanamiz. Agar javobda xato olsak, xabarni faylga yozamiz. Keling, ushbu kod bilan nima noto'g'ri ekanligini ko'rib chiqaylik. 1. S: Yagona javobgarlik printsipi  : Sinf (yoki funksiya/usuli) faqat bitta narsa uchun javobgar bo'lishi kerak. Agar sinf bir nechta muammolarni hal qilish uchun mas'ul bo'lsa, uning ushbu masalalarni hal qilishni amalga oshiradigan quyi tizimlari bir-biriga bog'langan. Bunday quyi tizimlarning biridagi o'zgarishlar boshqasida o'zgarishlarga olib keladi. Bizning misolimizda loginUser funktsiyasi ikkita narsa uchun javobgardir - tizimga kirish va faylga xato yozish. Bitta javobgarlik bo'lishi uchun xatoni qayd etish mantig'i alohida sinfga joylashtirilishi kerak.
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())
        }
    }
}
Xatolarni qayd etish mantig'i alohida sinfga ko'chirildi va endi loginUser usuli faqat bitta javobgarlikka ega - avtorizatsiya. Agar xatolar jurnali mantig'ini o'zgartirish kerak bo'lsa, biz buni loginUser 2 funksiyasida emas, balki FileLogger sinfida qilamiz. O: Ochiq-yopiq  printsip: Dasturiy ta'minot ob'ektlari (sinflar, modullar, funktsiyalar) kengaytma uchun ochiq bo'lishi kerak , lekin o'zgartirish uchun emas. Shu nuqtai nazardan, kengaytirish uchun ochiqlik, agar zarurat tug'ilsa, sinfga, modulga yoki funksiyaga yangi xatti-harakatlar qo'shish qobiliyatidir va o'zgartirish uchun yopiqlik - bu dasturiy ta'minot ob'ektlarining manba kodini o'zgartirishni taqiqlash. Bir qarashda bu murakkab va qarama-qarshi tuyuladi. Ammo agar siz unga qarasangiz, printsip juda mantiqiy. OCP printsipiga rioya qilish, dasturiy ta'minot mavjud kodni o'zgartirish orqali emas, balki yangi kod qo'shish orqali o'zgartiriladi. Ya'ni, dastlab yaratilgan kod "daxlsiz" va barqaror bo'lib qoladi va yangi funksionallik amalga oshirish merosi orqali yoki mavhum interfeyslar va polimorfizmdan foydalanish orqali kiritiladi. Shunday qilib, FileLogger sinfini ko'rib chiqamiz. Agar jurnallarni boshqa faylga yozishimiz kerak bo'lsa, biz nomni o'zgartirishimiz mumkin:
class FileLogger{
    fun logError(error: String) {
        val file = File("errors2.txt")
        file.appendText(text = error)
    }
}
Ammo keyin barcha jurnallar yangi faylga yoziladi, bu bizga kerak emas. Ammo sinfni o'zgartirmasdan loggerimizni qanday o'zgartirishimiz mumkin?
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 va logError funksiyasini ochiq deb belgilaymiz, yangi CustomErrorFileLogger sinfini yaratamiz va yangi jurnal dasturini yozamiz. Natijada, bizning sinfimiz funksionallikni kengaytirish uchun mavjud, ammo o'zgartirish uchun yopiq. 3. L: Liskov almashtirish printsipi  . Subklasslar o'zlarining yuqori sinflari uchun o'rinbosar bo'lishi kerak. Ushbu tamoyilning maqsadi shundan iboratki, avlod sinflari dasturni buzmasdan, ular olingan ota-sinflar o'rniga ishlatilishi mumkin. Agar kod sinf turini tekshirayotgani aniqlansa, almashtirish printsipi buzilgan. Agar biz voris sinfini shunday yozgan bo'lsak:
class CustomErrorFileLogger : FileLogger() {

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

}
Endi esa FileLogger sinfini ombordagi CustomErrorFileLogger bilan almashtiramiz.
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())
        }
    }

}
Bunday holda, logError asosiy sinfdan chaqiriladi yoki siz qo'ng'iroqni fileLogger.logError(e.message.toString()) ga fileLogger.customErrorLog(e.message.toString()) ga o'zgartirishingiz kerak bo'ladi 4. I :  Interfeysni ajratish printsipi Muayyan mijoz uchun mo'ljallangan yuqori ixtisoslashtirilgan interfeyslarni yarating. Mijozlar o'zlari foydalanmayotgan interfeyslarga bog'liq bo'lmasligi kerak. Bu murakkab tuyuladi, lekin aslida juda oddiy. Masalan, FileLogger sinfini interfeysga aylantiramiz, lekin unga yana bitta funktsiya qo'shamiz:
interface FileLogger{

    fun printLogs()

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

}
Endi barcha avlodlardan printLogs funksiyasini amalga oshirish talab qilinadi, garchi bu bizga barcha avlod sinflarida kerak bo'lmasa ham.
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

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

}
va endi bizda bo'sh funktsiyalar bo'ladi, bu kod tozaligi uchun yomon. Buning o'rniga biz interfeysda standart qiymatni yaratishimiz va keyin funksiyani faqat kerakli sinflarda bekor qilishimiz mumkin:
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)
    }

}
Endi FileLogger interfeysini amalga oshiradigan sinflar toza bo'ladi. 5. D: Bog'liqlik inversiyasi printsipi  . Bog'liqlik ob'ekti aniq narsa emas, balki mavhumlik bo'lishi kerak. Keling, asosiy sinfimizga qaytaylik:
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())
        }
    }

}
Biz bu erda hamma narsani allaqachon sozlagan va tuzatganga o'xshaydi. Ammo o'zgartirish kerak bo'lgan yana bir nuqta bor. Bu FirebaseAuth sinfidan foydalanmoqda. Agar biror nuqtada biz avtorizatsiyani o'zgartirishimiz va Firebase orqali emas, balki, masalan, API so'rovidan foydalangan holda tizimga kirishimiz kerak bo'lsa nima bo'ladi? Keyin biz ko'p narsalarni o'zgartirishimiz kerak va biz buni xohlamaymiz. Buning uchun signInWithEmailAndPassword(email: String, password: String) funksiyasi bilan interfeys yarating:
interface Authenticator{
    fun signInWithEmailAndPassword(email: String, password: String)
}
Bu interfeys bizning abstraktimizdir. Va endi biz loginning o'ziga xos ilovalarini qilamiz
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) {
        //другой способ логина
    }

}
Va "MainRepository" sinfida endi aniq amalga oshirishga bog'liqlik yo'q, faqat abstraktsiyaga bog'liq.
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())
        }
    }

}
Va endi, avtorizatsiya usulini o'zgartirish uchun modul sinfidagi faqat bitta qatorni o'zgartirishimiz kerak.
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION