Hi! What is the recommended approach to create an ...
# announcements
m
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
.copy method for data classes?
m
thanks for response, but if it is not a data class?
it's an Entity class
s
Use a builder
g
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
@spand thanks for suggestion. Could you provide an example?
g
Because usually it’s just easier than create special builder
m
@gildor I mean Hibernate entity class
b
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
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
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
@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
Override hashCode/equals looks more tedious than write specific copy method
b
@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
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
That's what I usually do as well, and what
listOf
does.
g
It’s usually require much more code than copy/with function, tho more flexible and abstract
d
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
Yeah, I'd prefer an interface myself
m
@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
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.