I'm trying to implement a ValueObject base class using Kotlin and DDD. As any value object must consider all attributes in the equals method, I decided to implement a single method using reflection, so no subclass will need to implement it.
My ValueObject class is as follows:
abstract class ValueObject {
override fun equals(other: Any?) = when {
this === other -> true
other !is ValueObject -> false
else -> deepEquals(other)
}
inline fun <reified T : Any> deepEquals(other: T): Boolean {
val fields = T::class.java.declaredFields
println(T::class)
for (field in fields) {
field.isAccessible = true
val thisValue = field.get(this)
val otherValue = field.get(other)
if (thisValue != otherValue && (thisValue == null || !thisValue.equals(otherValue))) {
return false
}
}
return true
}
override fun hashCode() = hashFromReflection()
private fun hashFromReflection(): Int {
val fields = this::class.java.declaredFields
var result = 17
for (field in fields) {
field.isAccessible = true
val value = field.get(this)
result = 31 * result + (value?.hashCode() ?: 0)
}
return result
}
protected abstract fun validate() : Notification
}
I implemented the following code to test:
class PhoneNumber (val code: String, val number: String) : ValueObject() {
override fun validate(): Notification {
return Notification()
}
}
fun main() {
val phoneNumber = PhoneNumber("33", "w")
val other = PhoneNumber("3", "w")
println(phoneNumber == other)
println(phoneNumber.deepEquals(other))
}
However, the result is weird. If I call deepEquals directly, it works fine. But if I use == operator, it considers T as ValueObject (not PhoneNumber), finds no declaredFields, and gives me a wrong result:
class br.all.domain.common.ValueObject
true
class br.all.domain.common.PhoneNumber
false
What I'm doing wrong?
You seem to be overcomplicating this with
reified
. You can just get theClass
of objects by doingthis::class.java
orother::class.java
. There is no need forreified
.The reason why your code doesn't work is because there is no type information about the specific subclass of
ValueObject
when you calldeepEquals(other)
inequals
. The reified type parameter is simply passed asdeepEquals<ValueObject>(other)
.Also, you seem to be reinventing data classes. Consider doing this with data classes instead.