JavaRush /จาวาบล็อก /Random-TH /หลักการที่มั่นคงที่จะทำให้โค้ดของคุณสะอาดขึ้น
Paul Soia
ระดับ
Kiyv

หลักการที่มั่นคงที่จะทำให้โค้ดของคุณสะอาดขึ้น

เผยแพร่ในกลุ่ม
โซลิดคืออะไร? SOLID ย่อมาจาก: - S: Single  Responsibility Principle - O:  หลักการเปิด-ปิด - L: หลักการทดแทน Liskov  . - I: หลักการแยกส่วนต่อ  ประสาน - D: หลักการผกผันการ  พึ่งพา หลักการ 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 ถูกส่งผ่านในตัวสร้าง และเราใช้มันเพื่อพยายามเข้าสู่ระบบ หากเราได้รับข้อผิดพลาดในการตอบกลับ เราจะเขียนข้อความลงในไฟล์ ตอนนี้เรามาดูกันว่ามีอะไรผิดปกติกับรหัสนี้ 1. S: Single Responsibility Principle  : คลาส (หรือฟังก์ชัน/เมธอด) ควรรับผิดชอบเพียงสิ่งเดียวเท่านั้น หากคลาสมีหน้าที่รับผิดชอบในการแก้ปัญหาต่างๆ ระบบย่อยที่ใช้แก้ไขปัญหาเหล่านี้จะเชื่อมต่อถึงกัน การเปลี่ยนแปลงในระบบย่อยหนึ่งนำไปสู่การเปลี่ยนแปลงในระบบย่อยอื่น ในตัวอย่างของเรา ฟังก์ชัน 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: หลักการ เปิด-ปิด  : เอนทิตีซอฟต์แวร์ (คลาส โมดูล ฟังก์ชัน) จะต้องเปิดสำหรับส่วนขยาย แต่ไม่ใช่เพื่อการดัดแปลง ในบริบทนี้ การเปิดกว้างต่อการขยายคือความสามารถในการเพิ่มพฤติกรรมใหม่ให้กับคลาส โมดูล หรือฟังก์ชัน หากจำเป็น และความปิดในการเปลี่ยนแปลงถือเป็นข้อห้ามในการเปลี่ยนซอร์สโค้ดของเอนทิตีซอฟต์แวร์ เมื่อมองแวบแรก สิ่งนี้ฟังดูซับซ้อนและขัดแย้งกัน แต่ถ้าคุณดูหลักการก็ค่อนข้างสมเหตุสมผล ตามหลักการ 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. L: หลักการทดแทน Liskov  . มีความจำเป็นที่คลาสย่อยสามารถทำหน้าที่เป็นการแทนที่ซูเปอร์คลาสได้ วัตถุประสงค์ของหลักการนี้คือ คลาสที่สืบทอดมาสามารถใช้แทนคลาสพาเรนต์ที่ได้รับมาโดยไม่ทำให้โปรแกรมเสียหาย หากปรากฎว่าโค้ดกำลังตรวจสอบประเภทของคลาส แสดงว่ามีการละเมิดหลักการทดแทน หากเราเขียนคลาสทายาทดังนี้:
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. ฉัน : หลักการ แยกส่วนต่อประสาน สร้างอินเทอร์เฟซพิเศษที่ออกแบบมาสำหรับลูกค้าเฉพาะราย ไคลเอนต์ไม่ควรขึ้นอยู่กับอินเทอร์เฟซที่พวกเขาไม่ได้ใช้ ฟังดูซับซ้อน แต่จริงๆ แล้วง่ายมาก ตัวอย่างเช่น เรามาสร้างคลาส 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. D: หลักการผกผันการพึ่งพา เป้าหมายของการพึ่งพาควรเป็นสิ่งที่เป็นนามธรรม ไม่ใช่สิ่งที่เป็นรูปธรรม กลับไปที่ชั้นเรียนหลักของเรา:
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