什么是固体? 以下是 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())
}
}
}
现在,要更改授权方法,我们只需要更改模块类中的一行。
GO TO FULL VERSION