The open-Closed principle with functional interfaces (in Kotlin)

The open-Closed principle with functional interfaces (in Kotlin)

Summary

If we don’t follow the open-closed principle, thousands of conditions would be checked in our code. We can fix it with Kotlin SAM.

Are you familiar with the SOLID open-closed principle? Let’s take a look at it:

The Open-Closed Principle (OCP)?states that software entities (classes, modules, methods, etc.)?should be open for extension, but closed for modification. In practice, this means creating software entities whose behavior can be changed without the need to edit and recompile the code itself.

Bad Customer

Here’s how our code would look if we didn’t follow the open-close principle: Consider we have a data class of Customers:

data class BadCustomer(val amount: Int, val paymentMethod: String)

        

We have three ways to calculate payments:


creditcard Checkout
debitCard Checkout
cash Checkout
        

As an example, I would probably write the following code to calculate every customer’s payment based on their payment method:

when (it.paymentMethod) {

    "creditCardCheckout" -> {
        println(it.amount * 10)
    }

    "debitCardCheckout" -> {
        println(it.amount * 20)
    }

    "cashCheckout" -> {
        println(it.amount * 30)
    }
}        

Problem:

What if we want to add another payment method? Would it be easy to add a new condition to the code? How would it be if we had thousands of payment methods? Do you think adding thousands of conditions is okay?

No alt text provided for this image

Good Customer

This is where Kotlin SAM can help me.

With a SAM conversion, Kotlin can?convert any lambda expression whose signature matches the signature of the interface’s single method into the code, which dynamically instantiates the interface implementation

The following links provide more information about SAM in Kotlin and Java:

To set up our payment method, we need to create a SAM:

fun interface Checkout {
    fun charge(amount: Int): Int
}        

And then we should change the data class:

data class Customer(
    val amount: Int,
    val paymentMethod: Checkout
)        

The data class now contains a Checkout instead of a String. Next, I want to implement the Checkout in my code for every payment method that I want to offer:

val creditCardCheckout = Checkout {
    it * 10
}
val debitCardCheckout = Checkout {
    it * 20
}
val cashCheckout = Checkout {
    it * 30
}        

Using my new data class, I want to create a list of Customers

val customerList = mutableListOf<Customer>()

customerList.add(Customer(75, creditCardCheckout))
customerList.add(Customer(50, debitCardCheckout))
customerList.add(Customer(25, cashCheckout))        

Instead of a string, I’m now passing a Checkout to my customer.

Finally, my code is free of unnecessary conditions:

customerList.forEach {
    println(it.paymentMethod.charge(it.amount))
}
        
No alt text provided for this image
Massoud Beygi

Results-Driven Team Player | Continuous Learner

2 年

Perfect

要查看或添加评论,请登录

社区洞察

其他会员也浏览了