https://kotlinlang.org logo
#announcements
Title
# announcements
m

Maksim Vlasov

08/05/2019, 10:21 AM
Hi! What is the recommended approach to create an object, mutate it a little bit and then make it immutable? I use the following steps right now. A parent object has children mutable fields. Firstly, I create a parent object. Secondly, I create a child object. Then I set the parent object to the child object field. Thirdly, I set child field to the parent object. Lastly, I persist parent object.
g

gildor

08/05/2019, 10:22 AM
.copy method for data classes?
m

Maksim Vlasov

08/05/2019, 10:26 AM
thanks for response, but if it is not a data class?
it's an Entity class
s

spand

08/05/2019, 10:29 AM
Use a builder
g

gildor

08/05/2019, 10:32 AM
What is Entity class? Anyway, in Kotlin I tend to use completely immutable object with some copy method (like copy for data class but manually created)
m

Maksim Vlasov

08/05/2019, 10:33 AM
@spand thanks for suggestion. Could you provide an example?
g

gildor

08/05/2019, 10:33 AM
Because usually it’s just easier than create special builder
m

Maksim Vlasov

08/05/2019, 10:33 AM
@gildor I mean Hibernate entity class
b

Big Chungus

08/05/2019, 10:36 AM
Why can't you use data class for hibernate entity?
Another thing you could do is have all your object's properties be provided via custom delegate. Then implement the delegate to check for let's say
isLocked
property uppon setter invocation which is false by default and throw an exception if it's true. finally have a lock() method in your entity to set it to true when you;re done mutating.
It's not ideal, but that'll at least give you runtime safety for object's mutations.
m

Maksim Vlasov

08/05/2019, 10:39 AM
I don't use data class for hibernate entity, because it generates wrong equals(), hashCode(), toString() by default. However, I could override it.
Anyway, it seems strage to use data class just for copy method(
b

Big Chungus

08/05/2019, 10:50 AM
Here's a delegate snippet:
Copy code
abstract class LockableEntity(isLocked: Boolean = false) {
  private var isLocked = false
  fun lock() {
    isLocked = true
  }

  protected class LockableProperty<T>(initialValue: T) {
    private var value: T = initialValue
    operator fun getValue(thisRef: LockableEntity, prop: KProperty<*>): T {
      return value
    }

    operator fun setValue(thisRef: LockableEntity, prop: KProperty<*>, value: T) {
      if (!thisRef.isLocked) {
        this.value = value
      } else {
        throw IllegalStateException("${thisRef::class.simpleName} is locked! Further property modification is forbidden")
      }
    }
  }
}

class SomeHibernateEntity() : LockableEntity() {
  var x: String by LockableProperty("Initial")
}

fun main() {
  val tmp = SomeHibernateEntity()
  tmp.x = "modified"
  tmp.lock()
  
  tmp.x = "Shouldn't succeed"
}
m

Maksim Vlasov

08/05/2019, 10:59 AM
@Big Chungus thank you! You recommend to use the approach only for mutable properties, aren't you? Could an Entity has immutable properties as well with such approach?
g

gildor

08/05/2019, 10:59 AM
Override hashCode/equals looks more tedious than write specific copy method
b

Big Chungus

08/05/2019, 11:08 AM
@Maksim Vlasov sure, just don't use the delegate for those and go straight for a plain
val x:Any = ???
👍 1
Here's an extended snippet:
Copy code
import kotlin.reflect.*

abstract class LockableEntity(private var isLocked: Boolean = false) {
  fun lock() {
    isLocked = true
  }

  protected class LockableProperty<T>(initialValue: T) {
    private var value: T = initialValue
    operator fun getValue(thisRef: LockableEntity, prop: KProperty<*>): T {
      return value
    }

    operator fun setValue(thisRef: LockableEntity, prop: KProperty<*>, value: T) {
      if (!thisRef.isLocked) {
        this.value = value
      } else {
        throw IllegalStateException("${thisRef::class.simpleName} is locked! Further property modification is forbidden")
      }
    }
  }
}

class SomeHibernateEntity(x: String, y: Int, val immutable: Int) : LockableEntity() {
  var x by LockableProperty<String>(x)
  var y: Int  by LockableProperty(y)
}

fun main() {
  val tmp = SomeHibernateEntity("1", 2, 3)
  tmp.x = "1 modified"
  tmp.lock()

  tmp.x = "Shouldn't succeed"
}
Or you can also do that via interface
Copy code
interface ILockableEntity {
  fun isLocked(): Boolean
  fun lock()

  class LockableProperty<T>(initialValue: T) {
    private var value: T = initialValue
    operator fun getValue(thisRef: ILockableEntity, prop: KProperty<*>): T {
      return value
    }

    operator fun setValue(thisRef: ILockableEntity, prop: KProperty<*>, value: T) {
      if (!thisRef.isLocked()) {
        this.value = value
      } else {
        throw IllegalStateException("${thisRef::class.simpleName} is locked! Further property modification is forbidden")
      }
    }
  }
}

class SomeHibernateEntity(x: String, y: Int, val immutable: Int) : ILockableEntity {
  private var locked = false
  override fun isLocked() = locked

  override fun lock() {
    locked = true
  }

  var x by ILockableEntity.LockableProperty(x)
  var y: Int  by ILockableEntity.LockableProperty(y)
}

fun main() {
  val tmp = SomeHibernateEntity("1", 2, 3)
  tmp.x = "1 modified"
  tmp.lock()

  tmp.x = "Shouldn't succeed"
}
And also interface with property-based approach
Copy code
interface ILockableEntity {
  val isLocked:Boolean
  fun lock()

  class LockableProperty<T>(initialValue: T) {
    private var value: T = initialValue
    operator fun getValue(thisRef: ILockableEntity, prop: KProperty<*>): T {
      return value
    }

    operator fun setValue(thisRef: ILockableEntity, prop: KProperty<*>, value: T) {
      if (!thisRef.isLocked) {
        this.value = value
      } else {
        throw IllegalStateException("${thisRef::class.simpleName} is locked! Further property modification is forbidden")
      }
    }
  }
}

class SomeHibernateEntity(x: String, y: Int, val immutable: Int) : ILockableEntity {
  override val isLocked: Boolean
    get() = locked
  
  private var locked = false

  override fun lock() {
    locked = true
  }

  var x by ILockableEntity.LockableProperty(x)
  var y: Int  by ILockableEntity.LockableProperty(y)
}

fun main() {
  val tmp = SomeHibernateEntity("1", 2, 3)
  tmp.x = "1 modified"
  tmp.lock()

  tmp.x = "Shouldn't succeed"
}
s

streetsofboston

08/05/2019, 12:19 PM
What about having an interface with only read-only properties (only `val`s) and having a data class implement them with `var`s. When you're done mutating the data class instance, pass it as its parent-interface to code that can only read it. (You can do
override var
on a
val
)
💯 1
k

karelpeeters

08/05/2019, 2:27 PM
That's what I usually do as well, and what
listOf
does.
g

gildor

08/05/2019, 2:54 PM
It’s usually require much more code than copy/with function, tho more flexible and abstract
d

Dico

08/05/2019, 5:14 PM
Abstract isn't necessarily a plus for entities especially, I might pick builder approach here. But I probably wouldn't use hibernate in the first place.
b

Big Chungus

08/05/2019, 6:31 PM
Yeah, I'd prefer an interface myself
m

Maksim Vlasov

08/06/2019, 11:23 AM
@gildor The problem with data class is the following: when a new copy is created, I have to update all child's to the new object(
g

gildor

08/07/2019, 1:58 AM
Would be nice to see real code from your example, how many fields, why is this problem to create child object in a same
copy
way.
8 Views