State and Identity

Value vs. Reference Objects

The Scala library distinguishes between values (AnyVal) and references (AnyRef).

More generally, value objects represent values such as quantities, times, coordinates, etc. Their key feature is that their identity is determined by their state (i.e., fields). Changing the state changes the identity. Therefore mutators-- methods that change the state-- are usually not provided.

Reference objects represent entities in the application domain, things like bank accounts, employees, and shopping carts. The identity of a reference object is determined by the entity that it represents, not its state. Changing the state doesn't change the identity.

Value Object Example

class Point2D(val xc: Double, val yc: Double) {
  override def equals(other: Any): Boolean =
    other match {
       case p: Point2D =>  p.isInstanceOf[Point2D] &&
                           (p.xc == this.xc) &&
                           (p.yc == this.yc)
       case _ => false
    }
  override def toString = "(" + xc + "," + yc + ")"
  override def hashCode = this.toString.hashCode
}

Here is a sample session:

scala> val p1 = new Point2D(3, 4)
p1: Point2D = (3.0,4.0)

scala> val p2 = new Point2D(3, 4)
p2: Point2D = (3.0,4.0)

scala> p1 == p2
res0: Boolean = true

scala> p1 eq p2
res1: Boolean = false

scala> val cities = collection.immutable.HashMap(p1->"New York")
cities: HashMap

scala> cities(p2)
res2: String = "New York"

Notes:

·       A 2 dimensional point is a good example of a value object. Its identity is determined by its state (i.e., the values of its xc and yc fields).

·       In Scala, the expression a == b parses to a.equals(b). The expression a eq b parses to a.eq(b).

·       In Scala, the default implementation of a.equals(b) returns true if a and b reference the same object. This is called literal equality. This is also the default implementation of a.eq(b).

·       For value objects we often need to override the definition of equals with code that compares fields. The example shows the standard way of doing this.

·       The expression a.## parses to a.hashcode.

·       If we want to use value objects as keys in hash tables, then we must override hashcode so that if a == b, then a.## == b.##

Reference Object Example

The classical example of a reference object is a bank account:

class Account(var balance: Double = 0.0)

A bank account is a good example of a reference object. Instances of Account represent account entities in the accounting domain.

Changing the balance of an account does not change its identity.

Having multiple instances of Account represent the same real world account is a bad idea. The instances can get out of synch with each other (i.e., come to have different balances) creating inconsistencies and errors. If our policy is to forbid multiple representations, then the default implementation of equals is correct, two accounts are equal if they are literally the same object.

Here's a sample session:

scala> val savings = new Account
savings: Account = Account@55d56113

scala> val savings2 = savings
savings2: Account = Account@55d56113

scala> val savings3 = new Account
savings3: Account = Account@68ceda24

scala> savings == savings2
res1: Boolean = true

scala> savings == savings3
res2: Boolean = false

scala> savings eq savings2
res3: Boolean = true

scala> savings eq savings3
res4: Boolean = false

Notes:

·       In the above example savings, savings2, and savings3 are reference objects. Savings and savings3 are literally equal.