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 TestDataClass
es directly, it's just running some function on each that returns an Any
, and comparing those Any
s - 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 Double
s 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