JavaRush /בלוג Java /Random-HE /עקרונות מוצקים שיהפכו את הקוד שלך לנקי יותר
Paul Soia
רָמָה
Kiyv

עקרונות מוצקים שיהפכו את הקוד שלך לנקי יותר

פורסם בקבוצה
מה זה SOLID? הנה מה שראשי התיבות SOLID מייצגים: - S: עיקרון אחריות יחידה  . - O: עקרון פתוח-סגור  . - ל: עקרון החלפת ליסקוב  . - I: עקרון הפרדת ממשק  . - D: עקרון היפוך תלות  . עקרונות מוצקים מייעצים כיצד לעצב מודולים, כלומר. לבנים מהם בנויה האפליקציה. מטרת העקרונות היא לעצב מודולים ש: - מקדמים שינוי - קלים להבנה - ניתנים לשימוש חוזר אז בואו נסתכל על מה הם עם דוגמאות.
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. ל: עקרון החלפת ליסקוב  . יש צורך שתת-מחלקות יוכלו לשמש כתחליף למחלקות העל שלהן. המטרה של עיקרון זה היא שניתן להשתמש בכיתות צאצאים במקום כיתות האב שמהן הן נגזרות מבלי לשבור את התוכנית. אם יתברר שהקוד בודק את סוג המחלקה, אז עקרון ההחלפה מופר. אם כתבנו את מחלקת היורש כך:
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 : עקרון הפרדת ממשק. צור ממשקים מיוחדים במיוחד המיועדים ללקוח ספציפי. לקוחות לא צריכים להיות תלויים בממשקים שהם לא משתמשים בהם. זה נשמע מסובך, אבל זה בעצם מאוד פשוט. לדוגמה, בואו נהפוך את המחלקה 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)
    }

}
ועכשיו יהיו לנו פונקציות ריקות, וזה רע לניקיון הקוד. במקום זאת, אנו יכולים ליצור ערך ברירת מחדל בממשק ולאחר מכן לעקוף את הפונקציה רק ​​במחלקות בהן היא נחוצה:
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(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