Backward Compatibility: Practice

Welcome back! Today, we'll master what we learned about backward compatibility in practice. Prepare to apply all the knowledge to practical tasks, but first, let's look at two examples and analyze them.

Task 1: Enhancing a Complex Data Processing Function with Function Overloading

Let's say that initially, we have a complex data processing class designed to operate on a list of maps, applying a transformation that converts all string values within the maps to uppercase. Here's the initial version:

class DataProcessor {
    fun processData(items: List<Map<String, Any>>) {
        val processedItems = items.map { item ->
            item.mapValues { entry ->
                if (entry.value is String) {
                    (entry.value as String).uppercase()
                } else {
                    entry.value
                }
            }
        }
        processedItems.take(3).forEach { println("Processed Item: $it") }
    }
}

We intend to expand this functionality, adding capabilities to filter the items based on a condition and to allow for custom transformations. The aim is to retain backward compatibility while introducing these enhancements. Here's the updated approach using function overloading:

class DataProcessor {
    fun processData(items: List<Map<String, Any>>) {
        processData(items, { true }, null)
    }

    fun processData(items: List<Map<String, Any>>, transform: ((Map<String, Any>) -> Map<String, Any>)?) {
        processData(items, { true }, transform)
    }

    fun processData(items: List<Map<String, Any>>, condition: (Map<String, Any>) -> Boolean, transform: ((Map<String, Any>) -> Map<String, Any>)?) {
        val processedItems = items.filter(condition).map { item ->
            transform?.invoke(item) ?: item.mapValues { entry ->
                if (entry.value is String) {
                    (entry.value as String).uppercase()
                } else {
                    entry.value
                }
            }
        }
        processedItems.take(3).forEach { println("Processed Item: $it") }
    }
}

// Usage examples:
fun main() {
    val data = listOf(
        mutableMapOf("name" to "apple", "quantity" to 10),
        mutableMapOf("name" to "orange", "quantity" to 5)
    )

    val processor = DataProcessor()

    // Default behavior - convert string values to uppercase
    processor.processData(data)

    // Custom filter - select items with a quantity greater than 5
    processor.processData(data, { it["quantity"] as Int > 5 }, null)

    // Custom transformation - convert names to uppercase and multiply the quantity by 2
    processor.processData(data) { item ->
        item.mapValues { entry ->
            if (entry.key == "name") {
                (entry.value as String).uppercase()
            } else {
                (entry.value as Int) * 2
            }
        }
    }
}

In this evolved version, we've introduced function overloading with additional parameters: (Map<String, Any>) -> Boolean to filter the input list based on a given condition, and ((Map<String, Any>) -> Map<String, Any>)? for custom transformations of the filtered items. The default behavior processes all items, converting string values to uppercase, which ensures that the original functionality's behavior is maintained for existing code paths.

Notice, that in the usage examples of the processData function, we utilize Kotlin's trailing lambda feature. This feature allows us to pass a lambda expression outside of the parentheses when it is the last parameter. For instance, in the custom transformation example:

processor.processData(data) { item ->
    item.mapValues { entry ->
        if (entry.key == "name") {
            (entry.value as String).uppercase()
        } else {
            (entry.value as Int) * 2
        }
    }
}

Here, the lambda expression { item -> ... } is passed as the last argument to the processData function, demonstrating the trailing lambda syntax.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal