JavaRush /Java блогу /Random-KY /Кодуңузду тазараак кыла турган SOLID принциптери
Paul Soia
Деңгээл
Kiyv

Кодуңузду тазараак кыла турган SOLID принциптери

Группада жарыяланган
SOLID деген эмне? Бул жерде SOLID аббревиатурасы эмнени билдирет: - S: Single  Responsibility Principle. - О: Ачык-жабык принцип  . - Л: Лисковдун алмаштыруу принциби  . - I: Interface Segregation Principle  . - D: Көз карандылыктын инversion принциби  . 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 конструктордо өткөрүлөт жана биз аны кирүү үчүн колдонобуз. Эгер жоопто ката алсак, билдирүүнү файлга жазабыз. Эми бул code менен эмне туура эмес экенин карап көрөлү. 1. S: Бирдиктүү жоопкерчorк принциби  : Класс (же функция/ыкма) бир гана нерсе үчүн жооптуу болушу керек. Эгерде класс бир нече маселелерди чечүүгө жооптуу болсо, анда бул маселелердин чечorшин ишке ашыруучу анын подсистемалары бири-бири менен байланышкан. Мындай бир подсистеманын өзгөрүшү экинчисинин өзгөрүшүнө алып келет. Биздин мисалда loginUser функциясы эки нерсеге жооп берет - кирүү жана файлга ката жазуу. Бир жоопкерчorк болушу үчүн катаны жазуу логикасы өзүнчө класска жайгаштырылышы керек.
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 методунда бир гана жоопкерчorк бар - авторизация. Эгерде каталарды каттоо логикасын өзгөртүү керек болсо, анда биз муну loginUser 2 функциясында эмес, FileLogger классында жасайбыз. O: Ачык-жабык  Принцип: Программалык an objectилер (класстар, модулдар, функциялар) кеңейтүү үчүн ачык болушу керек , бирок өзгөртүү үчүн эмес. Бул контекстте кеңейтүү үчүн ачыктык - бул класска, модулга же функцияга жаңы жүрүм-турумду кошуу мүмкүнчүлүгү, ал эми өзгөртүүгө жабыктык - бул программалык камсыздоо an objectилеринин баштапкы codeун өзгөртүүгө тыюу салуу. Бир караганда, бул татаал жана карама-каршы угулат. Бирок карап көрсөң, принцип абдан логикалуу. OCP принцибине ылайык, программалык камсыздоо бар codeду өзгөртүү менен эмес, жаңы codeду кошуу менен өзгөртүлөт. Башкача айтканда, алгач түзүлгөн code "кол тийбеген" жана туруктуу бойдон калууда жана жаңы функция ишке ашыруунун мурастоосу аркылуу же абстрактуу интерфейстерди жана полиморфизмди колдонуу аркылуу киргизилет. Келгиле, 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 классын түзүп, жаңы журналды ишке ашырууну жазабыз. Натыйжада, биздин класс функцияларды кеңейтүү үчүн жеткorктүү, бирок өзгөртүү үчүн жабык. 3. L: Liskov алмаштыруу принциби  . Субкласстар алардын суперкласстарынын ордуна кызмат кыла алышы зарыл. Бул принциптин максаты – программаны бузбастан, алар алынган ата-эне класстардын ордуна тукум класстарды колдонууга болот. Эгерде code класстын түрүн текшерип жатканы аныкталса, анда алмаштыруу принциби бузулат. Эгерде биз мураскор классты мындайча жазсак:
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()) деп өзгөртүүгө туура келет 4. I :  Interface Segregation Principle Белгилүү бир кардар үчүн иштелип чыккан жогорку адистештирилген интерфейстерди түзүңүз. Кардарлар алар колдонбогон интерфейстерге көз каранды болбошу керек. Бул татаал угулат, бирок бул чындыгында абдан жөнөкөй. Мисалы, FileLogger классын интерфейс кылалы, бирок ага дагы бир функция кошолу:
interface FileLogger{

    fun printLogs()

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

}
Эми бардык урпактардан printLogs функциясын ишке ашыруу талап кылынат, ал тургай, бардык тукум класстарда бизге кереги жок.
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

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

}
эми бизде бош функциялар болот, бул codeдун тазалыгына жаман. Анын ордуна, интерфейсте демейки маанини жасап, андан кийин функцияны ал керек болгон класстарда гана жокко чыгара алабыз:
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. D: Көз карандылыктын инversion принциби  . Көз карандылыктын an objectиси конкреттүү нерсе эмес, абстракция болушу керек. Келгиле, негизги классыбызга кайрылалы:
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(email: String, password: String) функциясы менен интерфейс түзүңүз:
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