lambda design patterns
At the beginning of learning programming and Android, writing code for me was to learn the language and the tools it provides to finish the job or the task. After some point of writing code, you realize there is some abstraction that could solve the problems with your code.
Problems make you rewrite a big block of codes with every new requirement or update old features. So abstraction came to save us time and effort; in the OOP world, this abstraction came in many shapes, depending on the problem it tries to solve.
It's called in the software industry as Design Patterns, and they may be the OOP in action if I want to describe it. You can't feel the importance of OOP and its beauty without learning them.
With shifting towards functional programming, it is necessary to spot patterns that make the code easier to reason. For example, one of the basic principles of Functional programming is Lambda.
Lambda, in simple words, is just the anonymous function; you declare it in Java by an interface with a single function. for example, if you used a Runnable may look like this
Runnable runnable = new Runnable() {
@Override
public void run() {
//do something
}
};
if you're writing this in IntelliJ or Android Studio, it shows you a hint that you could change that to Lambda
clicking the option, and the new syntax will be changed to this
Runnable runnable = () -> {
//do something
};
What is the difference? Well, first of all, it's shorter, "less code," second, it gives you only the signature of the function you use, and in the Runnable case, it's just a void function.
And the way of writing lambda differs in Kotlin, as it doesn't require you to define an interface with a single function. It does that in compile time to be interpolatable with Java. But it doesn't require extra effort. In Kotlin, you could write something like this.
val voidFunction = {
//do something
}
and when you try to define the type, it will show you the matching signatures like this
Lambda is just an anonymous function without a name; you could define it in the go, think of it like it's a block of code that you defer its execution.
Thus, you could change to Lambda, any interface with a single function (SAM); all you have to do is define the arguments types and the return type, simply the function signature.
How does that could shape some of the famous Design patterns in the OOP world?
Strategy Pattern
class Printer(private val stringFormatterStrategy: Formatter) {
fun printString(string: String) {
println(stringFormatterStrategy.format(string))
}
}
interface Formatter {
fun format(string: String): String
}
val lowerCaseFormatter: Formatter = object : Formatter {
override fun format(string: String): String {
return string.toUpperCase()
}
}
val upperCaseFormatter: Formatter = object : Formatter {
override fun format(string: String): String {
return string.toUpperCase()
}
}
The idea of strategy pattern is to code to the interface, not the implementation, so the argument of the Printer in the pre-Lambda world could be an interface called Formatter. So you have two separate classes of different implementations; in the consumer code, the Printer doesn't care about the implementation; it only cares that the argument should be a Formatter.
But with Lambda, you only need to define a lambda signature. And path whatever implementation you want.
class Printer(private val stringFormatterStrategy: (String) -> String) {
fun printString(string: String) {
println(stringFormatterStrategy.invoke(string))
}
}
val upperCaseFormatter: (String) -> String = { it.toUpperCase() }
val lowerCaseFormatter: (String) -> String = { it.toLowerCase() }
Command Pattern
The idea of command pattern is to decouple the what from when, what you want to run, from when you want to run it, for example, you could save a set of code to run it later, the contract of the command pattern is just an interface with an execute function.
interface OrderCommand {
fun execute()
}
class OrderAddCommand(val id: Long) : OrderCommand {
override fun execute() = println("Adding order with id: $id")
}
class OrderPayCommand(val id: Long) : OrderCommand {
override fun execute() = println("Paying for order with id: $id")
}
class CommandProcessor {
private val queue = ArrayList<OrderCommand>()
fun addToQueue(orderCommand: OrderCommand): CommandProcessor =
apply {
queue.add(orderCommand)
}
fun processCommands(): CommandProcessor =
apply {
queue.forEach { it.execute() }
queue.clear()
}
}
with Lambda
class OrderAddCommand(val id: Long) : () -> Unit {
override fun invoke() = println("Adding order with id: $id")
}
class OrderPayCommand(val id: Long) : () -> Unit {
override fun invoke() = println("Paying for order with id: $id")
}
class CommandProcessor {
private val queue = ArrayList<() -> Unit>()
fun addToQueue(orderCommand: () -> Unit): CommandProcessor =
apply {
queue.add(orderCommand)
}
fun processCommands(): CommandProcessor =
apply {
queue.forEach { it.invoke() }
queue.clear()
}
}
And you could use it in both cases like this
CommandProcessor()
.addToQueue(OrderAddCommand(1L))
.addToQueue(OrderAddCommand(2L))
.addToQueue(OrderPayCommand(2L))
.addToQueue(OrderPayCommand(1L))
.processCommands()
Something different in this example, I declared the class with a lambda type, so it forces you to implement the invoke function, and the use case for this, if you have to pass some data to use later. The moment Lambda is invoked. It has the data it needs.
Observer Pattern
We use the observer pattern when we want to get the updates of an object state. A simple example of this is the UI listeners. Like the click listener or the text change listener in the edit text, most Ui platforms provide you with a set of listeners to behave accordingly.
An old habit is that we declare Ui listeners as one interface with many functions. In that case, you can't use Lambda. So it's better to untie that interface and make each function as SAM.
If you are an Android developer, you should be familiar with TextWatcher.
public interface TextWatcher extends NoCopySpan {
public void beforeTextChanged(CharSequence s, int start,
int count, int after);
public void onTextChanged(CharSequence s,
int start, int before, int count);
public void afterTextChanged(Editable s);
}
Here you can't just listen to text changes. You'll still have to override the other two, which is annoying, but the good news is that androidx core package provide nicer syntax. So you use what you need.
val editText = EditText(this)
editText.addTextChangedListener {
//do something
}
So observer pattern became just passing Lambda.
The idea of passing code that will be executed later is the key to understanding functional programming. This code is usually a lambda. All we need is to define the function signature. What remains is just an implementation detail.
lambda design patterns
if we could write a summer function in the old imperative way, it may look like this
fun totalValues(list: List<Int>): Int {
var sum = 0
for (element in list) {
sum += element
}
return sum
}
What if we need the sum of just the even values? Well, a straightforward solution, we could copy past this function and add a slight modification.
fun totalEvenValues(list: List<Int>): Int {
var sum = 0
for (element in list) {
if (element % 2 == 0) sum += element
}
return sum
}
Now a new requirement came, what if we need the sum of odd values? You got it right; we copy-paste and create a third function.
fun totalOddValues(list: List<Int>): Int {
var sum = 0
for (element in list) {
if (element % 2 != 0) sum += element
}
return sum
}
I think you got the idea, but now you have a powerful tool you could use; let's guess what piece of code you've changed each time. And we could pass it to our full function, so we defer the implementation to the user of this function.
The condition, we test element, and if it passes the test, we add it to the sum, in Lambda could be written like this.
fun totalValues(list: List<Int>, selector: (Int) -> Boolean): Int {
var sum = 0
for (element in list) {
if (selector(element)) sum += element
}
return sum
}
Now it's just one function.
//sum of even numbers
println(totalValues(listOf(1, 2, 3, 4)) { it % 2 == 0 })
//sum of odd numbers
println(totalValues(listOf(1, 2, 3, 4)) { it % 2 != 0 })
This Lambda we wrote could be found as a Predicate interface in Rxjava or even in the functional interfaces in Java8. We've achieved until now that we've extracted the changing part of code, which made it more flexible.
I hope you understand the importance of using Lambda and how it could change the way you write code.
useful resources