Home > Mobile >  Custom comparison for the enum class
Custom comparison for the enum class

Time:02-23

I have a data class TestDataClass that has a custom equals() method, in order to consider the given threshold while comparing the Price property that looks like:

    private val THRESHOLD: Double = 1e-7
    
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as TestDataClass

        if (id != other.id) return false
        if (kotlin.math.abs(price - other.price) > THRESHOLD) return false
        if (curr != other.curr) return false
        if (desc != other.desc) return false
        if (quantity != other.quantity) return false

        return true
      }

Then there is an enum class that builds its properties based on the objects of the TestDataClass as:

enum class TestDataClassColumns(private val expr: (TestDataClass) -> Any) {
  Price({ it.price }),
  Curr({ it.curr }),
  Description({ it.desc }),
  Quantity({ it.quantity });
}

Furthermore, I have a checkData() method in the enum class TestDataClassColumns that acts is used for the comparison and returns the properties that were not equal:

fun checkData(obj1: TestDataClass?, obj2: TestDataClass?): Boolean = expr(obj1) != expr(obj2)

Null check is there, just removed it from post to have more visibility.

So far, the equals() of TestDataClass works correct, however I have no idea, how to incorporate the THRESHOLD into the checkData() of enum class TestDataClassColumns(). Any ideas how to do it? The overall design should remain the same, meaning that the classes should be there. Here is the link to playground with some examples - Kotlin Playground

CodePudding user response:

In order to access the THRESHOLD from TestDataClassColumns, you have to move it to the companion object of your TestDataClass and make it public.

Then you can alter your TestDataClassColumns as follows:

enum class TestDataClassColumns(
    private val expr: (TestDataClass) -> Any, 
    private val uneq: (TestDataClass, TestDataClass) -> Boolean = { x,y -> x != y }
) {
  Price({ it.price }, { x, y -> kotlin.math.abs(x.price - y.price) > TestDataClass.THRESHOLD }),
  Curr({ it.curr }),
  Description({ it.desc }),
  Quantity({ it.quantity });

  fun checkData(obj1: TestDataClass?, obj2: TestDataClass?): Boolean = if (obj1 == null || obj2 == null) true else expr(obj1) != expr(obj2)
}

CodePudding user response:

checkData isn't comparing TestDataClasses directly, it's just running some function on each that returns an Any, and comparing those Anys - how would you apply a Double threshold there?

Seems like what you want is to take two TestDataClass instances and get them to compare themselves by one of their properties. That way they can handle each property appropriately, and if there is a threshold to consider it's an internal detail.

Probably the easiest way is to just create a price comparison function in your class, and make the equals function refer to it:

fun isSamePrice(other: TestDataClass) = kotlin.math.abs(price - other.price) <= THRESHOLD

override fun equals(other: Any?): Boolean {
    ...
    if (!isSamePrice(other)) return false
    ...
}

Then you can make your enum call that - instead of having a function that spits out values, which you then compare, you can have a function that uses two TestDataClass instances instead:

enum class TestDataClassColumns(private val compare: TestDataClass.(TestDataClass) -> Boolean) {
  Price({ isSamePrice(it) }),
  Curr({ curr == it.curr }),
  Description({ desc == it.desc }),
  Quantity({ quantity == it.quantity });

  fun checkData(obj1: TestDataClass?, obj2: TestDataClass?): Boolean =
    if (obj1 == null || obj2 == null) true else !obj1.compare(obj2)
}

All you need to do is initialise with a function that calls that comparison function on the class, and it's all handled internally - if the TestDataClass considers the prices equal, they are! And because it's internal, its equals function can call the same logic, which is important - keeps things consistent and all in the right place.


The downside is that all the others need a function that explicitly compares properties, which is nice and simple but there's the possibility someone won't wire up the correct property on one to the same on the other. You could create a comparison function for each property (isSameQuantity etc) so that how the comparison is made is always an internal detail, instead of making the enum decide for some of them.

You can also write the function call as a reference like this if you like:

enum class TestDataClassColumns(private val compare: TestDataClass.(TestDataClass) -> Boolean) {
  Price(TestDataClass::isSamePrice),

I used a lambda for consistency with the others, but if you did end up making a function for each, that might be neater!


Another option would be doing it your way, but instead of price being a Double, it's a custom type with its own equals function. That way you could init it with a double value, but when you compare it with another MyPrice by equality, it internally uses a threshold value. Basically you're getting around the fact that comparing Doubles just does straight equality, using your own type lets you tweak that behaviour. But obviously everything else has to handle the fact it's not a Double, and that value needs to be accessed through a property, etc

  • Related