JavaRush /Blog Java /Random-VI /Nguyên tắc RẮN sẽ làm cho mã của bạn sạch hơn
Paul Soia
Mức độ
Kiyv

Nguyên tắc RẮN sẽ làm cho mã của bạn sạch hơn

Xuất bản trong nhóm
RẮN là gì? Đây là từ viết tắt của SOLID: - S: Nguyên tắc trách nhiệm duy nhất  . - O: Nguyên lý đóng mở  . - L: Nguyên lý thay thế Liskov  . - I: Nguyên tắc phân chia giao diện  . - D: Nguyên tắc đảo ngược phụ thuộc  . Nguyên tắc SOLID tư vấn cách thiết kế các mô-đun, tức là. những viên gạch mà từ đó ứng dụng được xây dựng. Mục đích của các nguyên tắc là thiết kế các mô-đun: - thúc đẩy sự thay đổi - dễ hiểu - có thể tái sử dụng. Vì vậy, hãy xem chúng là gì bằng các ví 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())
        }
    }
}
Chúng ta thấy gì trong ví dụ này? FirebaseAuth được truyền vào hàm tạo và chúng tôi sử dụng nó để cố gắng đăng nhập. Nếu chúng tôi nhận được lỗi khi phản hồi, chúng tôi sẽ ghi thông báo vào một tệp. Bây giờ hãy xem có gì sai với đoạn mã này. 1. S: Nguyên tắc trách nhiệm duy nhất  : Một lớp (hoặc hàm/phương thức) chỉ chịu trách nhiệm về một điều. Nếu một lớp chịu trách nhiệm giải quyết một số vấn đề, các hệ thống con của nó thực hiện giải pháp cho những vấn đề này sẽ được kết nối với nhau. Những thay đổi trong một hệ thống con như vậy sẽ dẫn đến những thay đổi trong một hệ thống con khác. Trong ví dụ của chúng tôi, hàm loginUser chịu trách nhiệm cho hai việc - đăng nhập và ghi lỗi vào tệp. Để có một trách nhiệm, logic ghi lại lỗi phải được đặt trong một lớp riêng.
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())
        }
    }
}
Logic ghi lỗi đã được chuyển sang một lớp riêng và giờ đây phương thức loginUser chỉ có một trách nhiệm - ủy quyền. Nếu logic ghi nhật ký lỗi cần được thay đổi thì chúng ta sẽ thực hiện việc này trong lớp FileLogger chứ không phải trong hàm loginUser 2. O: Nguyên tắc đóng-mở  : Các thực thể phần mềm (lớp, mô-đun, hàm) phải được mở để mở rộng, nhưng không phải để sửa đổi. Trong bối cảnh này, tính mở đối với phần mở rộng là khả năng thêm hành vi mới vào một lớp, mô-đun hoặc chức năng nếu có nhu cầu và tính đóng đối với thay đổi là việc cấm thay đổi mã nguồn của các thực thể phần mềm. Thoạt nhìn, điều này nghe có vẻ phức tạp và mâu thuẫn. Nhưng nếu bạn nhìn vào nó, nguyên tắc này khá logic. Tuân theo nguyên tắc OCP là phần mềm được thay đổi không phải bằng cách thay đổi mã hiện có mà bằng cách thêm mã mới. Nghĩa là, mã được tạo ban đầu vẫn “nguyên vẹn” và ổn định, đồng thời chức năng mới được giới thiệu thông qua kế thừa triển khai hoặc thông qua việc sử dụng các giao diện trừu tượng và tính đa hình. Vì vậy, hãy nhìn vào lớp FileLogger. Nếu cần ghi nhật ký vào một tệp khác, chúng ta có thể đổi tên:
class FileLogger{
    fun logError(error: String) {
        val file = File("errors2.txt")
        file.appendText(text = error)
    }
}
Nhưng sau đó tất cả nhật ký sẽ được ghi vào một tệp mới, rất có thể chúng tôi không cần. Nhưng làm thế nào chúng ta có thể thay đổi logger mà không thay đổi lớp đó?
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)
    }

}
Chúng tôi đánh dấu lớp FileLogger và hàm logError là mở, tạo lớp CustomErrorFileLogger mới và viết một triển khai ghi nhật ký mới. Do đó, lớp của chúng tôi có sẵn để mở rộng chức năng nhưng bị đóng để sửa đổi. 3. L: Nguyên tắc thay thế Liskov  . Điều cần thiết là các lớp con có thể đóng vai trò thay thế cho các lớp cha của chúng. Mục đích của nguyên tắc này là các lớp con có thể được sử dụng thay cho các lớp cha mà chúng được dẫn xuất mà không làm hỏng chương trình. Nếu mã đang kiểm tra loại của một lớp thì nguyên tắc thay thế bị vi phạm. Nếu chúng ta viết lớp kế thừa như thế này:
class CustomErrorFileLogger : FileLogger() {

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

}
Và bây giờ hãy thay thế lớp FileLogger bằng CustomErrorFileLogger trong kho lưu trữ
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())
        }
    }

}
Trong trường hợp này, logError sẽ được gọi từ lớp cha, hoặc bạn sẽ phải thay đổi lệnh gọi fileLogger.logError(e.message.toString()) thành fileLogger.customErrorLog(e.message.toString()) 4. Tôi : Nguyên tắc phân chia giao diện . Tạo các giao diện chuyên biệt cao được thiết kế cho một khách hàng cụ thể. Khách hàng không nên phụ thuộc vào các giao diện mà họ không sử dụng. Nghe thì có vẻ phức tạp nhưng thực ra nó rất đơn giản. Ví dụ: hãy biến lớp FileLogger thành một giao diện, nhưng thêm một hàm nữa vào nó:
interface FileLogger{

    fun printLogs()

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

}
Bây giờ tất cả các lớp con sẽ được yêu cầu triển khai hàm printLogs, ngay cả khi chúng ta không cần nó trong tất cả các lớp con.
class CustomErrorFileLogger : FileLogger{

    override fun printLog() {

    }

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

}
và bây giờ chúng ta sẽ có các hàm trống, điều này không tốt cho độ sạch của mã. Thay vào đó, chúng ta có thể tạo một giá trị mặc định trong giao diện và sau đó ghi đè hàm chỉ trong những lớp cần đến:
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)
    }

}
Bây giờ các lớp triển khai giao diện FileLogger sẽ sạch hơn. 5. D: Nguyên tắc đảo ngược phụ thuộc  . Đối tượng của sự phụ thuộc phải là một sự trừu tượng, không phải là một cái gì đó cụ thể. Hãy quay trở lại lớp học chính của chúng ta:
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())
        }
    }

}
Có vẻ như chúng tôi đã cấu hình và sửa mọi thứ ở đây. Nhưng vẫn còn một điểm nữa cần phải thay đổi. Đây là sử dụng lớp FirebaseAuth. Điều gì sẽ xảy ra nếu tại một thời điểm nào đó chúng ta cần thay đổi ủy quyền và đăng nhập không phải thông qua Firebase mà bằng cách sử dụng một số loại yêu cầu API chẳng hạn? Khi đó chúng ta sẽ phải thay đổi rất nhiều thứ và chúng ta không muốn điều đó. Để thực hiện việc này, hãy tạo một giao diện có chức năng signInWithEmailAndPassword(email: String, pass: String):
interface Authenticator{
    fun signInWithEmailAndPassword(email: String, password: String)
}
Giao diện này là sự trừu tượng của chúng tôi. Và bây giờ chúng tôi thực hiện cụ thể việc đăng nhập
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) {
        //другой способ логина
    }

}
Và trong lớp `MainRepository` giờ đây không còn phụ thuộc vào việc triển khai cụ thể mà chỉ phụ thuộc vào sự trừu tượng
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())
        }
    }

}
Và bây giờ, để thay đổi phương thức ủy quyền, chúng ta chỉ cần thay đổi một dòng trong lớp mô-đun.
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION