JavaRush /Java 博客 /Random-ZH /坚实的原则将使您的代码更加清晰
Paul Soia
第 26 级
Kiyv

坚实的原则将使您的代码更加清晰

已在 Random-ZH 群组中发布
什么是固体? 以下是 SOLID 缩写的含义: - S:单一 责任原则。-O:开闭原则 。-L:里氏替换原理 。- 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:单一职责原则 :一个类(或函数/方法)应该只负责一件事。如果一个类负责解决几个问题,那么它的实现这些问题解决方案的子系统就相互连接。其中一个子系统的变化会导致另一个子系统的变化。在我们的示例中,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:里氏代换原理 。子类必须能够替代其超类。这一原则的目的是可以使用后代类来代替派生它们的父类,而不会破坏程序。如果事实证明代码正在检查类的类型,则违反了替换原则。如果我们这样写后继类:
class CustomErrorFileLogger : FileLogger() {

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

}
现在让我们用存储库中的 CustomErrorFileLogger 替换 FileLogger 类
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.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(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