Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to update data classes implementing a common interface

I'm struggling with data classes and polymorphism. I want to benefit from the immutability, but still be able to update my state. For this, I'm hoping to be able to use the copy function.

Let's give an example. I have this class hierarchy:

interface Aging {
  val age : Int
}

data class Cheese (
  override val age : Int
  // more Cheese specific properties
) : Aging

data class Wine (
  override val age : Int,
  val grape : String
  // more Wine specific properties
) : Aging   

And now I want to be able to do something like this (but this does not work):

class RipeningProcess(){
  fun ripen(products : List<Aging>) =
    // Not possibe, no copy function on Aging
    products.map { it.copy(age = it.age + 1) } 
}

How can I create updated copies in a polymorphic way?

I've tried to give the interface a copy function, but if the subtypes have additional properties they don't override the copy function.
It's frustrating since I know the subtypes have that property, but I cannot utilize that knowledge in the interface.

like image 521
Stim Avatar asked Aug 09 '17 07:08

Stim


People also ask

Can a data class implement an interface?

Data class can implement interfaces and extend to other classes. The parameters of the class can be either val or var type.

Why we use data classes?

Data classes are one of the most used Kotlin features and for a good reason — they decrease the boilerplate code you need to write, enable features like destructuring and copying an object and let you focus on what matters: your app.

What is the difference between abstract class and interface in Kotlin?

Kotlin interfaces are similar to abstract classes. However, interfaces cannot store state whereas abstract classes can. Meaning, interface may have property but it needs to be abstract or has to provide accessor implementations. Whereas, it's not mandatory for property of an abstract class to be abstract.


1 Answers

[OP:] The best I've come up with is indeed to declare the copy function in the interface:

interface Aging {
  val age : Int
  fun copy(age : Int) : Aging
}

This works out of the box for data class subtypes without additional properties (i.e. Cheese from the question). For data class subtypes with additional properties you need to declare it explicitly, since the generated copy function does not override the one from the interface.

The subtype with it's age-copy implementation looks like:

data class Wine(
  override val age : Int,
  val grape : String
) : Aging {

  // Different parameter name, to avoid conflict with generated copy method
  override fun copy(_age: Int) = copy(age = _age)
}

Hoping for better solutions (or a Kotlin improvement ;) ).

Edit: updated to follow Ghedeons suggestion.

like image 66
Stim Avatar answered Jan 01 '23 22:01

Stim