How do you handle the case when several data class...
# getting-started
d
How do you handle the case when several data classes share common properties via an interface? Especially how do you manage code duplication when constructing each particular instance (you have to repeat the common props every time). Possibilities which I can think of: • don't manage, i.e. accept the code duplication • make the common props
lateinit var
and define
setCommonProps
on the interface • employ some reflection magic • ... Example:
Copy code
interface Animal { val name: String }
data class Cat(override val name: String, kittens: ...)
data class Dog(override val name: String, puppies: ...)

// how to avoid repeating the name (or generally all common props) in each instantiation
val cat = Cat(name = "cat", kittens = ...)
val dog = Dog(name = "dog", puppies = ...)
a
Interfaces allows you to have default implementations directly in the interface itself. So if you have properties/functions that does not change between implementing classes then add them directly to the interface. If they do vary you obviously have to implement them in each and every one of the classes themselves. You can also combine the approach so you have a default implementation in the interface itself and then only override it in the data classes that differ from that default implementation
This is an example
Copy code
interface Thing {
    val type: String

    fun description(): String {
        return "I am a thing"
    }
}
interface Animal: Thing {
    override val type: String get() = "Animal"
    fun bark()
}

interface UnknownThing: Thing { // Use default description for unknowns.
    override val type: String get() = "Unknown"
}

data class Dog(val name: String): Animal {

    override fun description(): String { // Overrides default description in Thing
        return "A dog is a four legged animal."
    }
    override fun bark() {
        TODO("Not yet implemented")
    }
}
d
@Andreas Dybdahl I'm asking not about the overriding but about the instantiation of concrete classes. I've added a simple example illustrating this.
a
@David Kubecka you can just override them in the class and not put them in the constructor like this:
Copy code
interface Animal { val name: String }
data class Cat(val kittens: Int): Animal {
    override val name: String = "Cat"
}
data class Dog(val puppies: Int): Animal {
    override val name: String = "Dog"
}

// how to avoid repeating the name (or generally all common props) in each instantiation
val cat = Cat(kittens = 7)
val dog = Dog(puppies = 10)
d
Well, in my case it would be inappropriate to set up default values.
a
What is the usecase? Is some of the going to be different values and some of them the same, or is all Dogs going to have the name "dog" for example? I all is the case you would put the value in the class and not the constructor, if some of them were different and some were not, I would put them in the constructor but then add a default value so it could be left out when constructing one etc. Depends on the usecase
c
Composition instead of inheritance: instead of implementing an interface with many fields, create a data-class with the common fields and have that as a field of the two other classes. This allows sharing DTOs etc for the entire common part.
👍 1
d
Thanks! As always, the concrete circumstance (MongoDB Document classes) prevent me from doing the right thing 😞 (or at least easily since embedding classes apparently is not as easy as with JPA)
c
You should be able to do this easily with MongoDB—that's what I'm using as well, and I use this strategy extensively.
d
Can you please elaborate? I'm new to MongoDB and quick search for embedding/nesting revealed just this: https://stackoverflow.com/a/63132705/13015027, i.e. writing a custom Converter. Is there an easier option?
c
I use #kmongo (https://litote.org/kmongo/) and it's as simple as
Copy code
@Serializable
data class Animal(
    val name: String,
)

@Serializable
data class Cat(
    val animal: Animal,
    …
)  

collection.save(Cat(Animal("Foo"), …))
I don't use Spring, so I can't tell you how it works there.
In general though, MongoDB is built around field nesting, so I highly recommend you find a solution for this soon, as you'll need it very often
d
I'm afraid I didn't express myself clearly the first time. Your example would serialize as
Copy code
{animal: {name: "Foo"}, <other fields>}
right? But I want it flattened:
{name: "Foo", <other fields>}
. In other words I want to achieve the same effect as with JPA's
@Embedded
(on Animal in your example).
c
Ah. Well, that's between you and your serialization library, good luck 🙂
plus1 1
d
If you're using Jackson, you could use
@Unwrap
. Other serializable library may have similar means.