Classes and objects

Classes and objects are programming constructs that enable us to abstract and group together data and related functionality into convenient design units both at a conceptual level (classes) and in terms of concrete runtime organization (objects instantiated from classes).

Let us consider one simple example to recall how to define classes and work with them.

Suppose we need to work with numerical values in an application so that we need to build derived values from existing values. We can support such an application in programming terms by first declaring an abstract base class (or trait) that enables us to read a numerical value and nothing else.

Let us declare such a base class (copy and paste this to the console to play yourself!):

abstract class Value:
  def read: Int
end Value

An abstract class in itself does not enable us to do anything unless we extend it with concrete classes that supply concrete implementations to the abstract methods.

Let us implement a value that evaluates to a constant that is given at construction time when we instatiate the class to a concrete object.

Observe how we declare the class to extend the abstract class and provide a concrete implementation to the abstract method:

class ConstantValue(c: Int) extends Value:
  def read = c
end ConstantValue

We can now start instantiating Value-objects at the console and read their values:

scala> val v = new ConstantValue(123)
v: ConstantValue = ConstantValue@4bd3db5c

scala> v.read
res: Int = 123

Beyond constant values, we can of course create a class whose values are variable and can be set (written) after the object is created. Let us introduce such a class:

class VariableValue(c: Int) extends Value:
  var myVal = c
  def write(v: Int) =
    myVal = v
  def read = myVal
end VariableValue

We observe that we can indeed now write values to a VariableValue-object and read the new value after a write:

scala> val v = new VariableValue(456)
v: VariableValue = VariableValue@5aef577f

scala> v.read
res: Int = 456

scala> v.write(111)

scala> v.read
res: Int = 111

Finally, let us create a class that reports the sum of two values. Observe how we use recursion on the two values v1 and v2 to get the sum:

class SumValue(v1: Value, v2: Value) extends Value:
  def read = v1.read + v2.read
end SumValue

At the console we observe that the value of a SumValue-object dutifully evaluates to the sum of the two component values as they evolve with writes:

scala> val v1 = new VariableValue(10)
v1: VariableValue = VariableValue@2b35b5d9

scala> val v2 = new VariableValue(20)
v2: VariableValue = VariableValue@e0a1ba9

scala> val s = new SumValue(v1,v2)
s: SumValue = SumValue@52de94b8

scala> s.read
res: Int = 30

scala> v1.write(0)

scala> s.read
res: Int = 20

scala> v2.write(0)

scala> s.read
res: Int = 0

We could now build arbitrarily complex sums of values consisting of constant and variable values and previous sum values. Accordingly, we could extend functionality further by introducing, say, negated values, difference values, product values, and so forth. This will be pursued in the exercises in the context of polynomials.