Define common properties without inheritance - kotlin

Is there a way of defining common properties without using inheritance in Kotlin?
For example
If I have two classes that both require an "id" property.
class Dog() {
var id: UUID?
}
class Cat() {
var id: UUID?
}
The general JAVA way to solve this is introduce a super class
class Animal() {
var id: UUID?
}
class Dog: Animal()
class Cat: Animal()
But now "Dog" and "Cat" are of type "Animal". What if I introduce a "Chair" class that also requires a unique identifier.
Essentially what I want to the ability to create a set of properties I can include in a number of different classes for programming convenience only. I don't want all the problems associated with inheritance.

You can, of course, use an interface instead of a base class:
interface HasId {
val id: UUID
}
data class Dog(override val id: UUID) : HasId
data class Cat(override val id: UUID) : HasId
However, the above is still using inheritance. If you have more common properties that would be used in multiple classes it may be a sign that they should be grouped together to form a separate value object e.g.
data class Address(val city: String, val street: String, val country: String)
class Person(val name: String, val address: Address)
class School(val name: String, val address: Address, val studentsCount: Int)
And if you want to treat Person and School uniformly with regards to address property you can still use the interface to denote the common attribute:
interface HasAddress {
val address: Address
}
class Person(val name: String,
override val address: Address) : HasAddress
class School(val name: String,
override val address: Address,
val studentsCount: Int) : HasAddress

It might be possible that delegation will suit your needs:
interface WithId {
var id: Int
}
class IdStorage : WithId {
override var id: Int = 0
}
class Dog(withId: WithId) : WithId by withId {
constructor() : this(IdStorage()) {}
}
class Cat(withId: WithId) : WithId by withId {
constructor() : this(IdStorage()) {}
}
This code is rather verbose, but what it allows you to do is:
Avoid using superclass just for the sake of having id property, which allows you to extend other classes if you need
Usage of interface, which guarantees other pieces of code that your class has id
Allows to move implementation of your properties (or functions) to separate class, hence no need for duplicate code in case of complex property/function implementation
Allows implementing multiple properties/functions in a separate class

As was mentioned in the comments:
interface Animal {
var id: UUID?
}
class Dog: Animal
class Cat: Animal

Related

Kotlin function for data class

What is the best way in Kotlin to write a function that can be used by a data class?
For example, say I have a data class and I need a function where the result is based on the value of a field from that data class:
data class Person(
val dateOfBirth: String
)
How would I go about writing an 'age' function for the Person object?
The same way you would write it for a non-data class!
You could add a method within the class:
data class Person(val dateOfBirth: String) {
fun age() = // …
}
Or you could add an extension method outside it:
data class Person(val dateOfBirth: String)
fun Person.age() = //…
(A method within the class is usually a better option if you can modify the class, and it belongs conceptually to the class. An extension method is useful if you don't have access to the class, or if it's specific to some particular usage or has a dependency on something unrelated to the class.)
Of course, you can always write a simple, old-style function:
fun calculateAge(person: Person) = // …
…but an extension method is clearer, reads better, and your IDE will suggest it.
In this case (where the age is quick to calculate, doesn't change the object's visible state, and won't throw an exception), a property or extension property might be more natural:
data class Person(val dateOfBirth: String) {
val age get() = // …
}
Or:
data class Person(val dateOfBirth: String)
val Person.age get() = //…
Then you can access it simply as myPerson.age.
The same way you would for any other class:
data class Person(val dateOfBirth: String) {
fun age(): Int {
// Use dateOfBirth here to compute the age.
}
}

Proper way to serialize a sealed class with kotlinx-serialization

I am not sure if it is possible yet but i would like to serialize the following class.
#Serializable
sealed class RestResponseDTO<out T : Any>{
#Serializable
#SerialName("Success")
class Success<out T : Any>(val value: T) : RestResponseDTO<T>()
#Serializable
#SerialName("Failure")
class Error(val message: String) : RestResponseDTO<String>()
}
when i try and use it
route(buildRoute(BookDTO.restStub)) {
get {
call.respond(RestResponseDTO.Success(BookRepo.getAll()))
}
}
I get this error:
kotlinx.serialization.SerializationException: Serializer for class
'Success' is not found. Mark the class as #Serializable or provide the
serializer explicitly.
The repo mentioned in the get portion of the route returns a list of BookDTO
#Serializable
data class BookDTO(
override val id: Int,
override val dateCreated: Long,
override val dateUpdated: Long,
val title: String,
val isbn: String,
val description: String,
val publisher:DTOMin,
val authors:List<DTOMin>
):DTO {
override fun getDisplay() = title
companion object {
val restStub = "/books"
}
}
This problem is not a deal breaker but it would be great to use an exhaustive when on my ktor-client.
Serializing sealed classes works just fine. What is blocking you are the generic type parameters.
You probably want to remove those, and simply use value: DTO. Next, make sure to have all subtypes of DTO registered for polymorphic serialization in the SerializersModule.

Avoid repetition of same logic

I have the following data classes:
sealed class ExampleDto
object Type1ExampleDto : ExampleDto()
object Type2ExampleDto : ExampleDto()
data class Type3ExampleDto(val name: Int, val age: Int) : ExampleDto()
data class Type4ExampleDto(val name: Int, val age: Int) : ExampleDto()
data class Type5ExampleDto(val email: String) : ExampleDto()
data class Type6ExampleDto(val name: Int, val age: Int, val email: String) : ExampleDto()
In particular, Type3ExampleDto, Type4ExampleDto and Type6ExampleDto share some common fields but it's important for my business logic to distinguish between types (i.e. even if Type3ExampleDto and Type4ExampleDto are identical, I have to know if I'm in the type3 or type4 case).
In one of my method I have the following call:
when (type) {
is Type3ExampleDto -> myMethod(type.vote, type.text)
is Type4ExampleDto -> myMethod(type.vote, type.text)
is Type6ExampleDto -> myMethod(type.vote, type.text)
else -> null
}
I find very ugly that I'm doing the same operation in all 3 cases and repeating the same line...
It makes sense to made Type3ExampleDto, Type4ExampleDto and Type6ExampleDto an implementation of some kind of interface just because only in this point I'm doing this kind of ugly repetition?
If all three dtos implement the following interface
interface MyInterface{
fun getVote() : Int
fun getText() : String
}
I can write:
if (type is MyInterface) {
myMethod(type.getVote(), type.getText())
}
So, it's acceptable to create this interface just to solve this isolated repetition?
Thanks
Note you can do it much more cleanly like this:
interface NameAndAgeDto {
val name: Int
val age: Int
}
data class Type3ExampleDto(override val name: Int, override val age: Int) : ExampleDto(), NameAndAgeDto
if (type is NameAndAgeDto) {
myMethod(type.name, type.age)
}
Whether it's "acceptable" is opinion. Looks fine to me.
You may change your model to have your logic based on behaviour instead of inheritance.
This way of modelling is based on principles of (but ain't exactly) Strategy Design Pattern.
interface HasName {
val name: String
}
interface HasAge {
val age: Int
}
interface HasEmail {
val email: String
}
object Type1
object Type2
data class Type3(
override val name: String,
override val age: Int
) : HasName, HasAge
data class Type4(
override val name: String,
override val age: Int
) : HasName, HasAge
data class Type5(
override val email: String
) : HasEmail
data class Type6(
override val name: String,
override val age: Int,
override val email: String
) : HasName, HasAge, HasEmail
// Then you can pass any object to it.
fun main(obj: Any) {
// Koltin type-casts it nicely to both interfaces.
if (obj is HasName && obj is HasAge) {
myMethod(text = obj.name, vote = obj.age)
}
}
fun myMethod(vote: Int, text: String) {
}
If you still want all the types to belong to some parent type, you can use marker interface (without any methods).
interface DTO
interface HasName {
val name: String
}
interface HasAge {
val age: Int
}
interface HasEmail {
val email: String
}
object Type1 : DTO
object Type2 : DTO
data class Type3(
override val name: String,
override val age: Int
) : HasName, HasAge, DTO
data class Type4(
override val name: String,
override val age: Int
) : HasName, HasAge, DTO
data class Type5(
override val email: String
) : HasEmail, DTO
data class Type6(
override val name: String,
override val age: Int,
override val email: String
) : HasName, HasAge, HasEmail, DTO
// Here, it is DTO instead of Any
fun main(obj: DTO) {
if (obj is HasName && obj is HasAge) {
myMethod(text = obj.name, vote = obj.age)
}
}
And use sealed class instead of marker interface if you need classes as enum.
In that case, when over sealed class is exhaustive with all options without null ->.

Share common properties between Kotlin classes

I have 2 (data) classes that almost share the same properties:
data class Foo(
val id: FooId,
val name: String,
... 10+ properties
}
data class NewFoo(
val name: String,
... 10+ properties
}
I just want some syntax sugar magic here: not to repeat 10+ properties. I can make a base sealed class, but you would end up writing even more text (for passing arguments to base class ctor), although you are safer from making a mistake.
Yes, I know I could use composition for this, but here I don't want to, as there might be different 'variants' of the same data.
Am I missing something or this is not possible in Kotlin?
You can use an abstract (or sealed) class with abstract params instead and override them in the constructor of your data class (i.e. without additional passing them into the constructor of the base class).
abstract class Base {
// put commons parameter here
// abstract param needs to be initialized in the constructor of data class
abstract val name: String
// you can define some not-abstract params as well
open lateinit var someOtherParam: String
}
data class Foo1(
override val name: String,
val id: Int,
val someAdditionalParam1: String
) : Base()
data class Foo2(
override val name: String,
val someAdditionalParam2: String,
override var someOtherParam: String
) : Base()

Data class constructor with two different constructor in Kotlin

I am new to Kotlin. I want to write a class which holds data. I want two constructor. What i want is something like this
class InstituteSearchDetails (var centerId: String) {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
init {
this.centerId=centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But on Secondary constructor line it says primary constructor call is required. I know some delegation is required which call primary constructor form there. I cant call primary constructor from here. I am sorry if i am doing some silly mistake. I am new to this thing
From the doc:
If the class has a primary constructor, each secondary constructor
needs to delegate to the primary constructor, either directly or
indirectly through another secondary constructor(s). Delegation to
another constructor of the same class is done using the this keyword:
Example:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
Your code:
constructor( instituteName: String, city: String) : this("centerId"){
this.instituteName=instituteName;
this.city=city;
}
But it doesn't look like you have the centerId value in the secondary constructor.
You can have two secondary constructors:
class InstituteSearchDetails {
lateinit var centerId: String;
lateinit var instituteName: String;
lateinit var city: String;
constructor(centerId: String) {
this.centerId = centerId
}
constructor( instituteName: String, city: String)
{
this.instituteName=instituteName;
this.city=city;
}
}
But be aware that, for instance, centerId wouldn't have been initialized if you use the second constructor and you will get an exception (UninitializedPropertyAccessException) if you try to access the centerId in that case.
Edit:
This is not possible in data class because data class requires a primary constructor with at least one val or var. If you have the primary constructor, then your secondary constructor should delegate to the primary constructor as well. Perhaps you can have all properties in a single primary constructor of a data class but with nullable properties. Or see Sealed class.
sealed class InstituteSearchDetails {
data class InstituteWithCenterId(val centerId: String): InstituteSearchDetails()
data class InstituteWithNameAndCity(val name: String, val city: String): InstituteSearchDetails()
}
fun handleInstitute(instituteSearchDetails: InstituteSearchDetails) {
when (instituteSearchDetails) {
is InstituteSearchDetails.InstituteWithCenterId -> println(instituteSearchDetails.centerId)
is InstituteSearchDetails.InstituteWithNameAndCity -> println(instituteSearchDetails.name)
}
}