Why do you think so?

Recently, I encountered a problem in a new project where there were many levels of nesting of fields with many resources, but the fields were all returned from an interface and stored in a JSON object. Problems:

  • When has too many nested levels
  • The when statement has an else condition, so the code is not secure
  • Late maintenance costs are high

With Kotlin, the syntax is much more streamlined, but this requirement inevitably makes the code less readable and less maintainable.

Actual requirements scenarios

Display of user resources. First resources types are divided into: Image, Video, and then the resource type can be divided into normal, red,’m, redBurning, state, divided into read noRead. The related fields returned by the server are stored in a JSON object and belong to the same level of attributes. So you get a data class that looks like this

    data class ResBean(val url: String, val format: String, val type: String, val viewStatus: String){
            companion object { // Define some constants in the associated object to represent the field types in it
            const val RES_FORMAT_VIDEO = "video"
            const val RES_FORMAT_IMAGE = "image"
            const val RES_RORMAT_UNKONW = "unKnow"
    
            const val RES_TYPE_NORMAL = "normal"
            const val RES_TYPE_RED = "red"
            const val RES_TYPE_RED_BURNING = "red_burning"
            const val RES_TYPE_BURNING = "burning"}}Copy the code

Now you need to display these resources in a ViewPager, first you need to think about Format, second you need to think about Type, and then you get a block of code for when nested when

fun showByFormatAndType(res: ResBean) {
    when (res.format) {
        ResBean.RES_FORMAT_IMAGE ->
            when (res.type) {
                ResBean.RES_TYPE_RED -> showRedImage()
                ResBean.RES_TYPE_BURNING -> showBurningImage()
                ResBean.RES_TYPE_RED_BURNING -> showRedBurningImage()
                ResBean.RES_TYPE_NORMAL -> showNormalImage()
            }
        ResBean.RES_FORMAT_VIDEO ->
            when (res.type) {
                ResBean.RES_TYPE_RED -> showRedVideo()
                ResBean.RES_TYPE_BURNING -> showBurningVideo()
                ResBean.RES_TYPE_RED_BURNING -> showRedBurningVideo()
                ResBean.RES_TYPE_NORMAL -> showNormalVideo()
            }
        else -> showUnKown()
    }
}
Copy the code

The main problem with such code is that it is difficult to maintain after readability, especially if you haven’t done a good job of packaging it up front. Imagine if you needed to add a new format, or a type that wasn’t comfortable. And you have to think about a lot of else cases

Implementation scheme

For hierarchical nesting, consider combining fromat and type as an integral type to reduce multiple levels of nesting, as follows:

fun byFormatAndTypeTodo(res: ResBean) {
    with(res) {
        when{ format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_NORMAL -> showNormalImage() format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_RED -> showRedImage() format == ResBean.RES_FORMAT_IMAGE && type ==  ResBean.RES_TYPE_BURNING -> showBurningImage() format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_RED_BURNING -> showRedBurningImage() format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_NORMAL -> showNormalVideo() format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_RED -> showRedVideo() format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_BURNING -> showBurningVideo() format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_RED_BURNING -> showRedBurningVideo()else -> showUnKown()
        }
    }
}
Copy the code

This reduces the nesting of layers, but at the expense of readability. Finally, consider incorporating sealing to improve readability. Start by defining a sealed class.

sealed class ResWithFormatAndType {
    data class NormalImageRes(val res: ResBean) : ResWithFormatAndType()
    data class RedImageRes(val res: ResBean) : ResWithFormatAndType()
    data class BurningImageRes(val res: ResBean) : ResWithFormatAndType()
    data class RedAndBurningImageRes(val res: ResBean) : ResWithFormatAndType()
    data class NormalVideoRes(val res: ResBean) : ResWithFormatAndType()
    data class RedVideoRes(val res: ResBean) : ResWithFormatAndType()
    data class BurningVideoRes(val res: ResBean) : ResWithFormatAndType()
    data class RedAndBurningVideoRes(val res: ResBean) : ResWithFormatAndType()
    object UnkownRes : ResWithFormatAndType()
}
Copy the code

Then encapsulate a method to get the corresponding

fun getResByFormatWithType(res: ResBean): ResWithFormatAndType = with(res) {
    when {
        format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_NORMAL -> NormalImageRes(res)
        format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_RED -> RedImageRes(res)
        format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_BURNING -> BurningImageRes(res)
        format == ResBean.RES_FORMAT_IMAGE && type == ResBean.RES_TYPE_RED_BURNING -> RedAndBurningImageRes(res)
        format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_NORMAL -> NormalVideoRes(res)
        format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_RED -> RedVideoRes(res)
        format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_BURNING -> BurningVideoRes(res)
        format == ResBean.RES_FORMAT_VIDEO && type == ResBean.RES_TYPE_RED_BURNING -> RedAndBurningVideoRes(res)
        else -> ResWithFormatAndType.UnkownRes
    }
}
Copy the code

Finally in the place of use

fun test(a) {
    when (getResWithFormatAndType(resBean) {
        is ResWithFormatAndType.NormalImageRes -> showNormalImage()
        is ResWithFormatAndType.BurningImageRes -> showBurningImage()
        is ResWithFormatAndType.RedImageRes -> showRedImage()
        is ResWithFormatAndType.RedAndBurningImageRes -> showRedBurningImage()
        is ResWithFormatAndType.BurningVideoRes -> showBurningVideo()
        is ResWithFormatAndType.NormalVideoRes -> showNormalVideo()
        is ResWithFormatAndType.RedAndBurningVideoRes -> showRedBurningVideo()
        is ResWithFormatAndType.RedVideoRes -> showRedVideo()
    }
}
Copy the code

The final solution is not perfect enough. I have seen the concept of pattern matching in Scala before, but I did not find the final solution in Kotlin. I hope Kotlin will support the concept of pattern matching soon.