JavaRush /Java блогы /Random-KK /Кодыңызды таза ететін SOLID принциптері
Paul Soia
Деңгей
Kiyv

Кодыңызды таза ететін SOLID принциптері

Топта жарияланған
SOLID дегеніміз не? SOLID аббревиатурасы мынаны білдіреді: - S: Бірыңғай  жауаптылық қағидасы. - O: Ашық-жабық принципі  . - Л: Лисковтың ауыстыру принципі  . - I: Интерфейсті бөлу принципі  . - 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: Бірыңғай жауаптылық қағидасы  : Класс (немесе функция/әдіс) тек бір нәрсеге жауапты болуы керек. Егер сынып бірнеше есептерді шешуге жауапты болса, оның осы есептерді шешуді жүзеге асыратын ішкі жүйелері бір-бірімен байланысты. Осындай бір ішкі жүйенің өзгеруі екіншісінің өзгеруіне әкеледі. Біздің мысалда 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 әдісінде бір ғана жауапкершілік бар - авторизация. Егер қателерді тіркеу логикасын өзгерту қажет болса, біз мұны loginUser 2 функциясында емес, FileLogger сыныбында орындаймыз. O: Ашық-жабық  принцип: Бағдарлама нысандары (сыныптар, модульдер, функциялар) кеңейту үшін ашық болуы керек , бірақ өзгерту үшін емес. Бұл контекстте кеңейтуге ашықтық - бұл қажет болған жағдайда сыныпқа, модульге немесе функцияға жаңа мінез-құлық қосу мүмкіндігі, ал өзгертуге жабықтық бағдарламалық жасақтама нысандарының бастапқы 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 сыныбын жасаймыз және жаңа тіркеу енгізуін жазамыз. Нәтижесінде біздің сынып функционалдылықты кеңейту үшін қол жетімді, бірақ өзгерту үшін жабық. 3. Л: Лисковтың алмастыру принципі  . Ішкі сыныптар өздерінің суперсыныптарын алмастыра алатындай болуы керек. Бұл принциптің мақсаты - ұрпақ кластарын бағдарламаны бұзбай, олар алынған ата-аналық сыныптардың орнына пайдалануға болады. Егер 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. Мен :  Интерфейсті бөлу принципі Белгілі бір клиентке арналған жоғары мамандандырылған интерфейстерді жасаңыз. Клиенттер пайдаланbyteын интерфейстерге тәуелді болмауы керек. Бұл күрделі естіледі, бірақ іс жүзінде бұл өте қарапайым. Мысалы, 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(электрондық пошта: Жол, құпия сөз: Жол) функциясымен интерфейс жасаңыз:
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