You have a data structure with internal state that can be mutated in place by a method call.
Make the internal state immutable, and convert the mutator into a method that makes a deep copy of the data structure, with the corresponding mutation applied.
class MutableEmployee(var name: String, var title: String) {
def setName(_name: String): Unit = {
= _name
name }
def setTitle(_title: String): Unit = {
= _title
title }
}
class ImmutableEmployee(name: String, title: String) {
def setName(_name: String): ImmutableEmployee =
new ImmutableEmployee(_name, title)
def setTitle(_title: String): ImmutableEmployee =
new ImmutableEmployee(name, _title)
}
This is a functional take on the builder pattern.
Mutable variables tend to preclude referential transparency, and can lead to all kind of bugs related to timing, threading, parallelism, lack of idempotency, evaluation order, lack of equational reasoning, etc.
val employee0 = new MEmployee("George Michael", "Employee")
println(s"employee0: ${employee0.name}, ${employee0.title}")
// employee0: George Michael, Employee
.setTitle("Mr. Manager")
employee0println(s"employee0: ${employee0.name}, ${employee0.title}")
// employee0: George Michael, Mr. Manager
Make internal state final. In Scala, this means simply replacing
var
with val
.
Make mutator methods, which probably have no useful return information, return the type of the data structure.
Change the implementations of mutator methods to construct new instances of the data structure, propagating the existing state plus the change(s) implied by the mutator.
In Scala, we get this pattern for free when we use case classes:
case class CCEmployee(name: String, title: String)
val employee1 = CCEmployee("George Michael", "Employee")
val employee2 = employee1.copy(title = "Mr. Manager")
println(s"employee1: ${employee1}")
println(s"employee2: ${employee2}")
employee1: CCEmployee(George Michael,Employee)
employee2: CCEmployee(George Michael,Mr. Manager)