Kotlin/Java enums are very powerful and important part of the Java language. Even though they can do so much, it is very common to see them either being used incorrectly or not used at all. In this blog post I want to remind you what Java enums can do and show some of the usage patterns that don’t get enough attention.

enum class State(val value: Short) {
    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4);

    companion object {
        fun valueOf(param: Short): State{
            return values().firstOrNull { it.value == param } ?: ONE
        }
    }
}

The valueOf() is a factory function, which return corresponded enum by a param which the value type is a Short.

@Test
fun testSimulateAStateConsumerEnd() {

	var result = when (State.valueOf(1)) {
		State.ONE -> "I"
		State.TWO -> "II"
		State.THREE -> "III"
		State.FOUR -> "IV"
		else -> "I"
	}

	Assert.isTrue(result == "I", "Test State Without OCP")
}

In real life we write code to perform based on the enum value at the consumer end.

E.g. if State’s value is equal to ONE, then return 1 and assign to result variable.

However, the downside of this is, if a new State is added down the road, then the consumer end must handle the new state. E.g. Add a new enum known as FIVE in line 6.

enum class State(val value: Short) {
    ONE(1),
    TWO(2),
    THREE(3),
    FOUR(4),
    FIVE(5);

    companion object {
        fun valueOf(param: Short): State{
            return values().firstOrNull { it.value == param } ?: ONE
        }
    }
}

E.g. if State is equal to FIVE, then return 5. This is no longer adhering OCP, as it requires modification at the consumer end like following State.FIVE -> 5 in line 9.

	@Test
	fun testSimulateAStateConsumerEndV2() {

		var result = when (State.valueOf(5)) {
			State.ONE -> "I"
			State.TWO -> "II"
			State.THREE -> "III"
			State.FOUR -> "IV"
			State.FIVE -> "V"
			else -> "I"
		}

		Assert.isTrue(result == "V", "Test State Without OCP V2")
	}

This is troublesome, as every time a new State is added, the consumer need to be modified to handle the new State. However we can solve this problem as followings:

Create an interface

interface IState {
    fun getResult(): String
}

Implement the interface

enum class State(val value: Short) : IState {
    ONE(1) {
        override fun getResult(): String {
            return "I"
        }
    },
    TWO(2) {
        override fun getResult(): String {
            return "II"
        }
    },
    THREE(3) {
        override fun getResult(): String {
            return "III"
        }
    },
    FOUR(4) {
        override fun getResult(): String {
            return "IV"
        }
    };

    companion object {
        fun valueOf(param: Short): State{
            return values().firstOrNull { it.value == param } ?: ONE
        }
    }
}
        @Test
	fun testSimulateAStateConsumerEndV3() {

		var result = State.valueOf(1).getResult()

		Assert.isTrue(result == "I", "Test State With OCP")
	}

With the implementation of the interface IState, each item within State enum, has to override a method known as getResult(). With this, each item is responsible to return the corresponded result back to the consumer.

Therefore, moving forward, if new State are added, this circumstances does not require the unit test method to handle a new state in the code. Which means continue adding new State E.g. (FIVE, SIX, and so on)does not require modification in the consumer.

Last modified: June 2, 2020

Author