Home > Enterprise >  Data class being used by other classes as parameter
Data class being used by other classes as parameter

Time:07-20

I want to use data classes to store user input and computed results. Then convert the data classes to Entity and update the data base.

The problem is I'm not clear on the best practices for using data classes.

I have read the documentation : https://kotlinlang.org/docs/data-classes.html along with other sources. However, they don't have more complex examples of how different classes are supposed to access and manipulate the data.

I am writing an android app in Kotlin that computes the volume and square feet of cube. It retrieves the last cube from a database and prints the result. Then it asks the user for the cube length. It computes and prints the volume, square feet, and cube length entered. Then it uploads the results to the database and repeats until the user exits. For simplicity the database only stores one cube.

I have attached a code example and have questions:

1.Is my example best practices how to use data classes.

2.If I use a data class as a parameter in class SquareFeetFormula() the data class in main will change the value of testData. Is it best to use .copy() to avoid unseen changes to the original data.

  1. If I just use a class instead of a data class question 2 dosen't happen.
import kotlin.math.*
fun main() {
    val input = CubeInput("My Cube", 3.0, "Original text")
    val result = CubeResult(27.0, 54.0)
    val volume = VolumeFormula()
    val squareFeet = SquareFeetFormula()
    val printInfo = PrintInfo()
    val database = DatabaseOperations()

    database.getDatabase()

    println("Your database Cube was ")
    printInfo.print(input, result)


    while (true) {
        try {
            println("Start")
            println("Enter the new length or press any key to finish")
            val stringInput = readLine()!!
            input.length = stringInput.toDouble()

//          If I don't do .copy() testData in input changes from "Original text" to "I Changed"
            volume.setInput(input.copy())
            squareFeet.setInput(input.copy())

            result.volume = volume.calculate()
            result.squareFeet = squareFeet.calculate()
            printInfo.print(input, result)

            database.updateDatabase()

        } catch (e: Exception) {
            println("Done")
            break
        }
    }
}


data class CubeInput(var name : String, var length : Double, var testData: String)
data class CubeResult(var volume : Double, var squareFeet : Double)

class VolumeFormula(){
    private lateinit var input :CubeInput

    fun setInput (input1 : CubeInput){
        input=input1
    }
    fun calculate () : Double{
        return input.length.pow(3)
    }
}

class SquareFeetFormula(){
    private lateinit var input :CubeInput

    fun setInput (input1 : CubeInput){
        input=input1
    }
    fun calculate () : Double{
        input.testData="I changed"
        return input.length*input.length*6
    }
}

class PrintInfo() {
    fun print(input: CubeInput, result: CubeResult) {
        println(input.name   " has a length of "  input.length   ", Width of " result.volume  ", and square feet of " result.squareFeet)
        println("TestData: "  input.testData)
    }
}

class DatabaseOperations(){
    fun getDatabase() {
//        populate input data class and result data class from database
    }

    fun updateDatabase() {
//        convert input and result into Entity
//        update Entity in database
    }
}

Thank you

CodePudding user response:

TL;TR:

  1. Better use val inside of your data class instead of var.
  2. Use copy when you want to change some of the values.
  3. The same happens when you use class instead of data class. Immutable properties (val) whill prevent that.

Even if you use class CubeInput instead of data class CubeInput, you can still change the object properties in SquareFeetFormula#calculate, because you pass the object by reference (here is an explanation of the difference between call by reference and call by value).

The implementation of your data class and the usage of copy kind of depends on your software design. In your case, I would design your data class like this:

// input parameter should be initialized and then be immutable
data class CubeInput(val name : String, val length : Double, val testData: String)

// once you calculated the result, its final, so immutable again
data class CubeResult(val volume : Double, val squareFeet : Double)

I usally use data class for classes which serves as somekind of value container. This means, whenever some value of the object should change, this would result in a new object, which differs from the orgin. Therefore I use copy. E.g.

val input = CubeInput("Input", 1.0, "Start")
// damn i forget, that every input length should get an offset
val updatedInput = input.copy(length = input.length 2.0)

Have fun coding.

CodePudding user response:

My opinion to your questions

  1. Usually in Kotlin we use data classes to represent data structure (i.e. only carries data but doesn't have behaviour). In your example, CubeInput and CubeResult are kind of data transfer objects which are very good illustration to use data classes.

  2. This is more about the application design. Usually we prefer immutable objects over mutable and Kotlin handles mutability by val and var keywords. @Elek already provided a very good illustration on how to change your code.

  3. You will face the same issue no matter you use class or data class.


One more thing about lateinit var.

When writing Kotlin we would like to use lateinit var with deliberation as it can make our code not null safe (which is one of the good things that Kotlin provides). If a user to your code forgets to call setInput, a null pointer exception will be thrown.

We usually write in this way in Kotlin:

  1. Make the formula classes not stateful: simply move the input to calculate method, i.e. calculate(input: CubeInput)
  2. Make the input available when constructing the formula, i.e.
class SquareFeetFormula(private val input: CubeInput){
    fun calculate () : Double {
        return input.copy(
           // logic here
        )
    }
}
  • Related