53

Kotlin, Data Class Inheritance by Delegation - nwillc - Medium

 4 years ago
source link: https://medium.com/@nwillc/kotlin-data-class-inheritance-by-delegation-2ad3fe6f9bd7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin, Data Class Inheritance by Delegation

A project I’m working on came across the following issue, it seems very specific to our situation but probably not. We had a Person class which had all the properties our domain used to describe a person, name, date of birth, address etc. We’d made it a data class to simplify working with all those properties. However, we treated as Person an entity, which is to say a Person, to be a Person, was assigned a distinct identifier. The problem arose around how to deal with a Person, before it was a Person, i.e. before it had been assigned its identifier. In the initial rough cut, the Person was given a nullable id:

data class Person(
val fName: String,
val lName: String,
val dob: LocalDate,
// ...
val id: UUID? = null
)

A nullable id?! Yeah I did that. It was expedient. Instantiate one, bounced it off the system of record, and get one back with its id set.

Another dev saw a nullable property, and immediately ripped out the property, renamed the class to PersonInfo, and made a new Person class with all the same fields, manually copied over, plus a non nullable id:

data class PersonInfo(
val fName: String,
val lName: String,
val dob: LocalDate,
// ...
)data class Person(
val fName: String,
val lName: String,
val dob: LocalDate,
// ...
val id: UUID
)

Then they added a sprinkling of transformation convenience methods. It was the only way they could see to have Person class with no id, and one with an id later, since you can’t subclass a data class. And so we sat and glowered at each other for a while. I understood the motivation but introducing data transfer objects so close to heart of a system will lead to systemic layering of transformations with each layer adding a bit more noise like the whisper game, until out at the edges a Person will be unrecognizable from the entity.

Then the other dev wondered aloud if we couldn’t employ delegation somehow. Eureka! Here’s what that led us to:

interface PersonDefinition(
val fName: String,
val lName: String,
val dob: LocalDate,
// ...
)data class Person(
private val data: PersonInfo,
val id: UUID
) : PersonDefinition by datadata class PersonInfo(
override val fName: String,
override val lName: String,
override val dob: LocalDate,
// ...
) : PersonDefinition

Wait… it’s up to two classes and an interface?! Yes. But here’s what this gets you:

  • There’s a single definition of the Person in the interface and changing it will, at the compiler level, force you to propagate the changes.
  • Everyone is a data class so that’s a win.
  • While Person doesn’t inherit from PersonInfo, it delegates to it for everything pertaining to PersonDefinition and so all the getters and setters etc pull through.
  • No translation code needed, a Person just composites in a PersonInfo.
  • No more nullables. Bye bye null checks on the id.

Was all this worth it to get rid of that nullable id? I’m not sure myself, but it kept the peace.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK