[Kotlin] Example of processing using Enum

at first

I've come to use Enums a lot when writing code, so I'll summarize the actual examples I used.

The code is written in Kotlin. Although the description method is different, I think that it can be implemented in Java with the same mechanism. (Maybe it can be done in other languages)

Code value ⇔ name conversion process

If there is a specification such as "Handle with code value (01 etc.) on the data, but want to display with name when displaying on the screen", conversion processing from code value to name and name to code value is required.

In the following, we will list the case of using simple conditional branching and the implementation method using Enum.

How to use conditional branching

Here are some codes I can think of:

    //Convert code to name
    fun convert(code : String) : String {

        if ("01" == code) {
            return "Name 1"
        } else if ("02" == code) {
            return "Name 2"
        }

        //After that, conditional branches are added for the number of code values.

        //If not applicable, return as is.
        //There are specifications here.
        return code
    }

The conditional branch can be a switch statement or a when statement. Since the above is the code value conversion process, it is also necessary to create the code value conversion process from the name.

How to use Enum

It can be implemented by using conditional branching, but two processes must be changed each time a code value is changed or added. It's simply annoying.

In the method using Enum, Enum is a class that can define variables and methods as well as enumeration of constants, so implement it so as not to write conditional branches as many as the number of codes.

By implementing in this way, extra conditional branches can be deleted, and even if there are changes or additions, only the definition of Enum can be changed.


enum class Test(val code : String, val value : String) {

    //If there is a change in the code value, fix it here
    KUBUN1("01", "Name 1"),
    KUBUN2("02", "Name 2"),
    KUBUN3("03", "Name 3");

    //Definition of static method in Java
    companion object {
        fun toValue(code : String) : String {
            //If there is an Enum enumeration, return it.
            for (item in enumValues<Test>()) {
                if (item.code == code) {
                    return item.value
                }
            }
            
            //If there is no applicable item, return the input as it is
            return code
        }

        fun toCode(value : String) : String {
            //If there is an Enum enumeration, return it.
            for (item in enumValues<Test>()) {
                if (item.value == value) {
                    return item.code
                }
            }

            //If there is no applicable item, return the input as it is
            return value
        }
    }
}

You can use the conversion process in a way like Test.toValue (arbitrary value).

Even in the above example, if a change is made, the class definition will be changed, but the difference from the example using conditional branching is that ** processing ** and ** data ** are separated. It is in. In the conditional branching example, the data and the process are integrated, so if you modify the data, you must also modify the process, but in the Enum example, you only need to change the data and you do not need to change the process. ..

To separate more data

If you don't want to have data in the class anyway, you can write the definition in an external file and read it to build the Enum. (Try Json)

Or register it in the database. In the first place, if the specification is to handle code values in processing, I feel that the data is often the value to be registered in the database, so this is more realistic.

Since these will affect the performance due to the increase in the number of file I / O and database access, it may be better to take measures such as singleton. At the same time, thread safety.

Serpentine

If you want to write the Enum class only for data and process it separately, you can implement it using the interface.


interface ITest {
    fun code() : String
    fun value() : String
}


enum class Test(val code : String, val value : String) : ITest {

    KUBUN1("01", "Name 1"),
    KUBUN2("02", "Name 2"),
    KUBUN3("03", "Name 3");

    override fun code(): String {
        return code
    }

    override fun value(): String {
        return value
    }

    companion object {
        //Java is OK if the array type is used as an interface
        //Kotlin is a class, so it's a bit of a hassle
        val VALUE : Array<ITest> = Arrays.copyOf(values(), values().size)
    }
}


class Hoge {
    companion object {
        fun toValue(items : Array<ITest>, code: String): String {
            //If there is an Enum enumeration, return it.
            for (item in items) {
                if (item.code() == code) {
                    return item.value()
                }
            }

            //If there is no applicable item, return the input as it is
            return code
        }

        fun toCode(items : Array<ITest>, value: String): String {
            //If there is an Enum enumeration, return it.
            for (item in items) {
                if (item.value() == value) {
                    return item.code()
                }
            }

            //If there is no applicable item, return the input as it is
            return value
        }
    }
}

fun main(args: Array<String>) {
    //Name 2 comes out
    println(Hoge.toValue(Test.VALUE, "02"))
}

The original conversion process receives the input at the interface, and the process inside calls the interface implementation process.

The reason for using the interface is that if you define various Enums but have the same purpose, you can realize the conversion process with one method by using the interface. Not needed if you only want to create one Enum.

Judgment processing by combining arbitrary items

There are items called processing status and status, and if you want to realize a process that returns boolean by the above combination, you need to check each item and return the result.

The combinations when the column is the processing status and the row is the status are shown below. Think of it as returning true for ○ and false for ×.

01 02 03
1 × ×
2 × × ×
3 ×

In the following, we will list the processing using conditional branching and the implementation method using Enum.

How to use conditional branching

There are the following methods. In this process, only true conditions are extracted, and the others are set as false and raised as conditions. You may describe all conditional branches.


    //Check input and return result
    fun validate(status : String, processStatus: String) : Boolean {

        if (status == "1" && processStatus == "01") {
            return true
        } else if (status == "3" && processStatus == "01") {
            return true
        } else if (status == "3" && processStatus == "03") {
            return true
        } else {
            return false
        }
    }

How to use Enum

The bad thing about ** how to use conditional branching ** is that it's unclear what each value used in conditional branching means. In most cases, what is represented by a code value should have some meaning.

Processing status "01" is waiting for processing, "02" is processed. State "1" is normal, "2" is an error. For example, this should mean something like this, but this conditional branch cannot read it.

In such cases, the method of defining constants and giving meaning is taken. If the number of constants is large, it becomes complicated, so we use Enum to summarize.

Now let's use Enums to create constants and give meaning to conditionals.

"Wait, was the merit of using Enums to make conditional branches meaningful?"

Yes, that's right. If you want meaning, you can use a constant or an Enum. (If you want to create a meaningful unit with constants as a design, I think Enum is good)

If you want to use the fact that Enum is a class, try to use it more like a class.

In the following processing, group by state (row) and create an Enum that returns boolean by the combination of processing status and state. The concept of processing is the same as the ** method using conditional branching **.


enum class StatusEnum {

    //Define Enum in state
    //True when the status is 1 and the processing status is 01
    STATUS_1 {
        override fun validate(status: String, processStatus: String): Boolean {
            return status == "1" && processStatus == "01"
        }
    },
    //State: 2 is always false
    STATUS_2 {
        override fun validate(status: String, processStatus: String): Boolean {
            return false
        }
    },
    //True when the status is 3 and the processing status is other than 02
    STATUS_3 {
        override fun validate(status: String, processStatus: String): Boolean {
            return status == "3" && processStatus != "02"
        }
    };

    abstract fun validate(status : String, processStatus : String) : Boolean

    //Check the information passed in here
    companion object {
        fun isStatusValidate(status : String, processStatus : String) : Boolean {
            for (item in values()) {
                val result = item.validate(status, processStatus)
                if (result) {
                    return result
                }
            }
            return false
        }
    }
}

Enum checks the state that it is in charge of while summarizing it in the state and giving meaning to the character literal itself, and the assumed isStatusValidate () used from the outside calls the Enum process and returns the result. I will. (If you want to have a meaning, the meaning of the state itself should be the variable name, but here it is the name as it is for example)

Therefore, if the status increases, the definition of Enum should be increased, and if the processing status changes, only the return processing of the affected Enum needs to be changed. You don't have to clutter long conditional statements.

Serpentine

In the above example, the process of checking the input value in the Enum process is described, but since the group is created in the state, I thought that it would be easier to convey the meaning if it was made into a variable. It was.

The following is an example of passing the state as input when generating an Enum.


enum class StatusEnum(val status : String) {

    //Define Enum in state
    //True when the status is 1 and the processing status is 01
    STATUS_1("1") {
        override fun validate(status: String, processStatus: String): Boolean {
            return this.status == status && processStatus == "01"
        }
    },
    //State: 2 is always false
    STATUS_2("2") {
        override fun validate(status: String, processStatus: String): Boolean {
            return false
        }
    },
    //True when the status is 3 and the processing status is other than 02
    STATUS_3("3") {
        override fun validate(status: String, processStatus: String): Boolean {
            return this.status == status && processStatus != "02"
        }
    };

    abstract fun validate(status : String, processStatus : String) : Boolean

    companion object {
        fun isStatusValidate(status : String, processStatus : String) : Boolean {
            for (item in values()) {
                val result = item.validate(status, processStatus)
                if (result) {
                    return result
                }
            }
            return false
        }
    }
}

Hmm. It's just like a snake.

If the variable name of the Enum uses ** meaning ** of the state ** as the name, "1" or "2" is defined as the specific value of the name, and is the state check the same as itself? It looks like, so it's easier to read ... but I feel like it doesn't matter which one you come to.

At the end

It turns out that using the fact that Enum is a class allows not only simple enumeration of constants, but also processing and property definition.

This leads to the deletion of unnecessary conditional statements and the simplification of the design (reducing the number of lines of code to be written, etc.), so I thought it was worth considering various things.

Recommended Posts

[Kotlin] Example of processing using Enum
Example of using vue.config.js
Example of params using where
Example of using abstract class
Example of using ArAutoValueConverter (field type conversion)
[Rails] Implementation of batch processing using whenever (gem)
Example of using addition faster than using StringBuilder (Java)
Java Enum utilization example
Implementation example of simple LISP processing system (Java version)
Summary of using FragmentArgs
Implementation example of simple LISP processing system (Ruby version)
[Java] [Kotlin] Generically call valueOf and values of Enum
Summary of using DBFlow
[Enum] Let's improve the readability of data by using rails enum
Execution of initial processing using Spring Boot Command Line Runner
Cleansing processing of Japanese sentences
Summary of using Butter Knife
Data processing using Apache Flink
[Swift] Asynchronous processing using PromiseKit
[Processing] Try using GT Force.
Csv output processing using super-csv
Summary of java error processing
Implementation of HashMap in kotlin
[Rails] Implementation of coupon function (with automatic deletion function using batch processing)
Implementation example of simple LISP processing system [Summary of each language version]