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 that
enables us to read a numerical value and nothing else. (For simplicity, let’s assume all numerical value are Int values.
Now, let us declare such a base class with a single method, read, that will be used to get the numerical value (copy and paste this to the console to play yourself!):
abstract class Value:
def read: Int
end Value
Note that the method read, is declared, but not implemented. This is allowed in abstract classes.
An abstract class in itself does not enable us to do anything unless we extend it with concrete classes that supply 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 = 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 by a write method) after the object
is created. Let us introduce such a class:
class VariableValue(c: Int) extends Value:
protected var _myVal = c
def write(v: Int) =
_myVal = v
def read = _myVal
end VariableValue
The variable _myVal now holds the current value of the variable value. It is declared protected to restrict access to VariableValue methods (and to inheriting classes).
We observe that we can indeed now write values to a VariableValue-object
and read the new value after a write:
scala> val v = 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 = VariableValue(10)
v1: VariableValue = VariableValue@2b35b5d9
scala> val v2 = VariableValue(20)
v2: VariableValue = VariableValue@e0a1ba9
scala> val s = 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
Note that the value of the sum changes as we update variable values, as expected in our application. This is because read is a method and implemented such that it reads the variable names every time it is being evaluated (and those read methods do the same).
We could now build arbitrarily complex sums of values consisting of constant and variable values and previous sum values. For example:
scala> val x = VariableValue(0)
val x: VariableValue = VariableValue@2a259f6f
scala> val y = VariableValue(0)
val y: VariableValue = VariableValue@4ad3969
scala> val z = VariableValue(0)
val z: VariableValue = VariableValue@2211e731
scala> val xyzSum = SumValue(x,SumValue(y,z))
val xyzSum: SumValue = SumValue@7f894a6f
scala> xyzSum.read
val res: Int = 0
scala> x.write(10)
scala> xyzSum.read
val res: Int = 10
scala> y.write(40)
scala> xyzSum.read
val res: Int = 50
scala> z.write(50)
scala> xyzSum.read
val res: Int = 100
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.