Open-Closed principle
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"
Sounds spooky doesn't it Well, if you need a new feature sometimes you cannot just notify everyone like:
"IDC if you your plane crashes, that function now needs and extra string as parameter"
A class can allow its behaviour to be extended without modifying the rest
Example class
class Footballer(private var name: String, private var age: Int, private var role: String) {
fun getFootballerRole(): String {
return when (role) {
"goalkeeper" -> "The footballer, ${this.name} is a goalkeeper"
"defender" -> "The footballer, ${this.name} is a defender"
"midfielder" -> "The footballer, ${this.name} is a midfielder"
"forward" -> "The footballer, ${this.name} plays in the forward line"
else -> throw Exception("Unsupported (╯°□°)╯︵ ┻━┻");
}
}
}
I hope we can all see where this is goin'
When a new player role emerge, we must modify this part, which can break other's code, or this will not be changed, and the new role will cause trouble wherever it's used
Solution for the class
Ok, so the last time we tried to make it work, it was a momentary solution now plan for the future, 'cause there is no such thing as finished code
A useful and common way to ensure the open-closed principle is to use inheritance, and never modify the parent class
Here comes the boilerplate, but keep in mind that now every role can behave differently, and the other's functionality won't be affected at all...
abstract class PlayerRole {
abstract fun getRole(): String
}
internal class GoalkeeperRole : PlayerRole() {
override fun getRole(): String {
return "goalkeeper"
}
}
internal class DefenderRole : PlayerRole() {
override fun getRole(): String {
return "defender"
}
}
internal class MidfieldRole : PlayerRole() {
override fun getRole(): String {
return "midfielder"
}
}
internal class ForwardRole : PlayerRole() {
override fun getRole(): String {
return "forward"
}
}
class FootballPlayer(private var name: String, private var age: Int, private var role: PlayerRole) {
fun getRole(): String {
return role.getRole()
}
}
It looks kinda similar to the dependency injection, but it's just one example
Example function
Here's another example, but now with a function
fun calculatePrice(price: Int, discount: String): Int {
if (discount == "Black Friday") {
return (price * 1.1).toInt()
} else if (discount == "Joe") {
return price / 2
} else if (discount == "outlet") {
return price / 3
}
/*...*/
return price
}
"Oh, so you want another kind of discount, and the if statement is already 10k lines long??!"
You might want to create a constant and extend that later
Solution for the function
In this case, each type is assigned to a discount value, and the maintenance will remain cheap
val discounts = arrayOf(
Pair("Black Friday", 1.1),
Pair("Joe", 0.5),
Pair("outlet", 0.33),
/* ... ,*/
Pair("Családvédelmi akcióterv", 3.0)
)
Now the calculate function can remain the same one-liner we learned from Haskell, our beloved