Kotlin SOLID 原则

本文介绍了面向对象编程中的SOLID原则在Kotlin中的应用,包括单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。通过示例展示了如何遵循这些原则编写更可维护、可扩展的代码,强调了它们在提高代码质量、降低维护成本方面的重要性。
摘要由CSDN通过智能技术生成

Kotlin SOLID 原则

Kotlin SOLID 原则

许多 Kotlin 开发者并不完全了解 SOLID 原理,即使他们知道,他们也不知道为什么要使用它。您准备好了解所有细节了吗?

介绍

亲爱的 Kotlin 爱好者,您好!欢迎来到我的新文章。今天我要讲的是 Kotlin 中的 SOLID 原则。首先,我将通过示例来解释什么是 SOLID 原则以及它们的用途。

什么是 SOLID 原则?

SOLID 是帮助创建可维护、可扩展和健壮软件的五个设计原则的首字母缩写词。Robert C. Martin 介绍了这些原则来指导程序员编写高质量的代码。虽然最初用于面向对象编程,但 SOLID 也适用于 Kotlin 等其他语言。这些原则旨在促进干净的代码和改进软件设计。SOLID原则如下:

  • 单一职责原则
  • 开闭原则
  • 里氏替换原则
  • 接口隔离原则
  • 依赖倒置原则
    现在,如果您准备好了,让我们通过正确使用和违反的示例来详细了解这些原则。

单一职责原则

单一职责原则(SRP)是面向对象编程中SOLID编程原则的一部分。它表示一个特定的类应该只有一个改变的目的。**这意味着一个类应该只有一个责任或工作。SRP通过保持类和函数的组织性和易于理解来维护它们很有用。**当一个类具有多重职责时,这些职责可能会无意中影响该类的其他任务或工作,从而导致意外行为、错误和增加的维护成本。

下面我们来看一下违规情况及其正确用法。

违反:

// Single Responsibility Principle Violation
// In this example the System class is trying to handle many different situation at the same time. 
// This approach can cause major problems in the future.
class SystemManager {
    fun addUser(user: User) { }
    fun deleteUser(user: User) { }
    fun sendNotification(notification:String) {}
    fun sendEmail(user: User, email: String) {}
}

违反单一职责原则
在这个例子中,系统类试图在同一个地方处理许多不同的情况。这种方法可能会在未来引起重大问题。

正确用法:

// Single Responsibility Principle Correct Usage:
// As seen in this example, we divided our System class into specific parts
// And placed the functions in their respective classes.

class MailManager() {
    fun sendEmail(user: User, email: String) {}
}

class NotificationManager() {
    fun sendNotification(notification: String) {}
}

class UserManager {
    fun addUser(user: User) {}
    fun deleteUser(user: User) {}
}

单一职责原则正确使用
如本例所示,我们将 System 类划分为特定的部分,并将函数放在它们各自的类中。

开闭原则

开放/封闭原则是面向对象设计中的一条规则,它表示类、模块、函数和其他软件实体应该对扩展开放但对修改关闭。这意味着您可以在不更改其原始代码的情况下向类中添加新内容。因此,无需更改类本身,您可以编写使用现有类添加新功能的新代码。这样做使代码更易于维护和重用。

下面我们来看一下违规情况及其正确用法。

违反:

// Open/Closed Principle Violation
// In this example, when we try to add something new to our class,
// we have to rewrite our existing code, which can cause problems later on.
class Shape(val type: String, val width: Double, val height: Double)

fun calculateArea(shape: Shape): Double {
    if (shape.type == "rectangle") {
        return shape.width * shape.height
    } else if (shape.type == "circle") {
        return Math.PI * shape.width * shape.width
    }
    return 0.0
}

违反开闭原则
在此示例中,当我们尝试向类中添加新内容时,我们必须重写现有代码,这可能会在以后引起问题。

正确用法:

// Open/Closed Principle Correct Usage
// As in correct usage, instead of changing the class itself,
// we wrote new classes using our existing class 
// and implemented our functions under new classes.

interface Shape {
    fun area(): Double
}

class Rectangle(val width: Double, val height: Double) : Shape {
    override fun area() = width * height
}

class Circle(val radius: Double) : Shape {
    override fun area() = Math.PI * radius * radius
}

fun calculateArea(shape: Shape) = shape.area()

开闭原则正确用法
按照正确的用法,我们不改变类本身,而是使用现有类编写新类,并在新类下实现我们的功能。

里氏替换原则

Liskov 替换原则是面向对象编程中的一个重要规则。它说如果你有一个程序可以处理某种类型的对象,你应该能够毫无问题地使用该对象的任何子类型。这意味着主类中的所有方法和属性也应该适用于所有子类,而无需更改任何内容。

下面我们来看一下违规情况及其正确用法。

违反:

// Liskov Substitution Principle Violation:
// As we saw in this example, the method we wrote in our main class should work properly in its subclasses according to the Liskov principle, 
// but when our subclass inherited from our superclass, our fly method did not work as expected.

open class Bird {
    open fun fly() {}
}

class Penguin : Bird() {
    override fun fly() {
        print("Penguins can't fly!")
    }
}

违反里氏替换原则
正如我们在这个例子中看到的,我们在主类中编写的方法根据 Liskov 原则应该在其子类中正常工作,但是当我们的子类继承自超类时,我们的 fly 方法没有按预期工作。

正确用法:

// Liskov Substitution Principle Correct Usage
// As you can see in this example, all the things we write in the superclass will be valid in the subclasses, 
// because we have implemented the method that is not valid for subclasses by creating an interface and implementing it where we need it.

open class Bird {
    // common bird methods and properties
}

interface IFlyingBird {
    fun fly(): Boolean
}

class Penguin : Bird() {
    // methods and properties specific to penguins
}

class Eagle : Bird(), IFlyingBird {
    override fun fly(): Boolean {
        return true
    }
}

里氏替换原则的正确用法
正如你在这个例子中看到的,我们在超类中写的所有东西在子类中都将有效,因为我们通过创建一个接口并在我们需要它的地方实现该接口来实现了对子类无效的方法。

接口隔离原则

接口隔离原则是制作计算机程序的规则。它说当我们制作程序的不同部分时,我们不应该以相同的方式制作它们。相反,我们应该让它们更小、更具体,这样程序的其他部分就不必依赖于它们不需要的东西。这有助于我们编写更易于更改和维护的代码,因为每个部分只做它需要做的事情。

下面我们来看一下违规情况及其正确用法。

违反:

// Interface Segregation Principle Violation
// When we look at our example, we see that the interface we created contains many methods.
// If we do everything inside a common interface, we may have made unnecessary use in the places that implement our interface.
// Instead, we can divide our system into smaller interface parts.

interface Animal {
    fun swim()
    fun fly()
}

class Duck : Animal {
    override fun swim() {
        println("Duck swimming")
    }

    override fun fly() {
        println("Duck flying")
    }
}

class Penguin : Animal {
    override fun swim() {
        println("Penguin swimming")
    }

    override fun fly() {
        throw UnsupportedOperationException("Penguin cannot fly")
    }
}

违反接口隔离原则
当我们查看我们的示例时,我们看到我们创建的接口包含许多方法。如果我们在一个公共接口内做所有事情,我们可能在实现接口的地方做了不必要的使用。相反,我们可以将我们的系统分成更小的接口部分。

正确用法:

// Interface Segregation Principle Correct Usage
// As we saw in the correct usage example, dividing the system into smaller interfaces and using them where we needed them made it much easier to change the system in the future.

interface CanSwim {
    fun swim()
}

interface CanFly {
    fun fly()
}

class Duck : CanSwim, CanFly {
    override fun swim() {
        println("Duck swimming")
    }

    override fun fly() {
        println("Duck flying")
    }
}

class Penguin : CanSwim {
    override fun swim() {
        println("Penguin swimming")
    }
}

接口隔离原则正确使用
正如我们在正确使用示例中看到的那样,将系统划分为更小的接口并在我们需要的地方使用它们使得将来更改系统变得更加容易。

依赖倒置原则

依赖倒置原则是一个 SOLID 原则,它指出高级模块不应该依赖于低级模块,但两者都应该依赖于抽象。这意味着类应该依赖于抽象,而不是具体的实现。DIP 背后的思想是将组件彼此解耦,这使得代码更加模块化、更易于测试和更易于维护。

下面我们来看一下违规情况及其正确用法。

违反:

// Dependency Inversion Principle Violation
// As we can see in this example, each of our payment methods is processed separately in our Service class in a hard code way.
// Instead of a hard code implementation, the system needed to be DEPEND to an abstract structure.

class PaymentService {
    private val paymentProcessorPaypal = PaypalPaymentProcessor()
    private val paymentProcessorStripe = StripePaymentProcessor()

    fun processPaymentWithPaypal(amount: Double): Boolean {
        return paymentProcessorPaypal.processPayment(amount)
    }

    fun processPaymentWithStripe(amount: Double): Boolean {
        return paymentProcessorStripe.processPayment(amount)
    }
}

class PaypalPaymentProcessor {
    fun processPayment(amount: Double): Boolean {
        // Process payment via Paypal API
        return true
    }
}

class StripePaymentProcessor {
    fun processPayment(amount: Double): Boolean {
        // Process payment via Stripe API
        return true
    }
}


fun main() {
    val paymentService = PaymentService()
    println(paymentService.processPaymentWithPaypal(50.0)) // Process payment via Paypal API
    println(paymentService.processPaymentWithStripe(50.0)) // Process payment via Stripe API
}

违反依赖倒置原则
正如我们在这个例子中看到的,我们的每一种支付方式都以硬编码的方式在我们的服务类中单独处理。系统需要依赖于抽象结构,而不是硬代码实现。

正确用法:

// In the correct usage example, we did not have to implement hard code about our payment methods in our Service class,
// because we set up an abstract structure with the interface that we created.

interface PaymentProcessor {
    fun processPayment(amount: Double): Boolean
}

class PaypalPaymentProcessor : PaymentProcessor {
    override fun processPayment(amount: Double): Boolean {
        // Process payment via Paypal API
        return true
    }
}

class StripePaymentProcessor : PaymentProcessor {
    override fun processPayment(amount: Double): Boolean {
        // Process payment via Stripe API
        return true
    }
}

class PaymentService(private val paymentProcessor: PaymentProcessor) {
    fun processPayment(amount: Double): Boolean {
        return paymentProcessor.processPayment(amount)
    }
}

fun main() {
    val paymentProcessor = PaypalPaymentProcessor()
    val paymentService = PaymentService(paymentProcessor)
    println(paymentService.processPayment(50.0)) // Process payment via Paypal API
}

在正确的用法示例中,我们不必在我们的服务类中实现关于我们的支付方法的硬代码,因为我们使用我们创建的接口设置了一个抽象结构。

结论

因此,SOLID 原则对于在 Kotlin 中创建可维护、可扩展且高效的软件至关重要。利用 Kotlin 的独特功能和构造,开发人员可以设计遵循这些准则的模块化、松散耦合的系统。坚持 SOLID 原则不仅可以提高代码的可测试性,还可以鼓励持续改进和最佳实践的文化。最终,在 Kotlin 开发中采用这些原则会产生更高质量的软件,这些软件可以得到有效维护并适应不断变化的需求。

在这里插入图片描述

参考

【kotlin语言】https://kotlinlang.org/
【Kotlin中的SOLID原则】https://proandroiddev.com/solid-design-principles-in-kotlin-79100c670df1
【SOLD原则:Kotlin之道】https://medium.com/the-android-caf%C3%A9/solid-principles-the-kotlin-way-ff717c0d60da
【SOLD原则与Kotlin示例】https://codersee.com/solid-principles-with-kotlin-examples/
【采用SOLID原则,编写干净可维护的代码】https://bootcamp.uxdesign.cc/adopting-solid-principles-for-clean-and-maintainable-code-with-kotlin-51e615c6b315

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Calvin880828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值