More on classes in Scala
Classes and objects in Scala
Recall our DJData
class from last time (which I’ve renamed to RockDJData
for the purposes of this lecture):
class RockDJData(val djName: String) { var numCallers: Int = 0 var queue: List[String] = List() def request(caller: String, song: String): String = { queue = queue :+ song numCallers += 1 if (numCallers % 1000 == 0) { "Congrats, " + caller + "! You get a prize from " + djName + "!" } else { "Cool, " + caller } } }
We can make an instance of this class and experiment with its functionality in the REPL:
scala> val djd = new RockDJData("DJ Doug") scala> djd.request("Alex", "Stairway to Heaven") "Cool, Alex" scala> djd.queue List("Stairway to Heaven")
Notice that we put new
before RockDJData
. Whenever we create an object in Scala,
we’ll have to write new
before the object name. Otherwise, our class seems to
behave much like its Python equivalent!
We can also add a play
method, to play the next song from the queue:
class RockDJData(val djName: String) { var numCallers: Int = 0 var queue: List[String] = List() def request(caller: String, song: String): String = { queue = queue :+ song numCallers += 1 if (numCallers % 1000 == 0) { "Congrats, " + caller + "! You get a prize from " + djName + "!" } else { "Cool, " + caller } } def play(): String = { val song = queue.head queue = queue.tail song } }
Scala lists have Pyret-style head
and tail
methods, to get the first element
of the list and the rest of the elements of the list respectively.
Access modifiers
In Python, we talked a lot about an important principle of object-oriented
program: encapsulation of data. We learned that as long as an object’s state is
only accessed via methods it defines–rather than by directly accessing its
fields–we can feel free to change its implementation without needing to change
any of the calling code. For instance, we don’t really need to access
numCallers
and queue
from outside the class: we can access them via the
request
method.
Unlike Python, Scala gives us a way to actually enforce this access pattern. We can make the fields private:
class RockDJData(private val djName: String) { private var numCallers: Int = 0 private var queue: List[String] = List() def request(caller: String, song: String): String = { queue = queue :+ song numCallers += 1 if (numCallers % 1000 == 0) { "Congrats, " + caller + "! You get a prize from " + djName + "!" } else { "Cool, " + caller } } def play(): String = { val song = queue.head queue = queue.tail song } }
Scala won’t let us build programs that access private fields from outside a class.
Testing Scala programs
First, let’s make one change to make our program easier to test:
class RockDJData(private val djName: String, private val prizeCaller: Int = 1000) { private var numCallers: Int = 0 private var queue: List[String] = List() def request(caller: String, song: String): String = { queue = queue :+ song numCallers += 1 if (numCallers % prizeCaller == 0) { "Congrats, " + caller + "! You get a prize from " + djName + "!" } else { "Cool, " + caller } } def play(): String = { val song = queue.head queue = queue.tail song } }
We’re using a library called ScalaTest to write tests for our Scala programs. We’ll put the following in a test file (see the lecture capture for details about creating a test file):
import org.scalatest.funsuite.AnyFunSuite class DJDataSuite extends AnyFunSuite { test("request method and prizeCaller") { val djd = new RockDJData("Alex", 2) val res1 = djd.request("Doug", "Stairway to Heaven") val res2 = djd.request("Doug", "Stairway to Heaven") assert(res1.contains("Cool")) assert(!res1.contains("Congrats")) assert(!res2.contains("Cool")) assert(res2.contains("Congrats")) } test("play method") { val djd = new RockDJData("Alex") intercept[NoSuchElementException] { djd.play() } // assert things about the exception djd.request("Doug", "Stairway to Heaven") djd.request("Doug2", "Wuthering Heights") assert(djd.play() == "Stairway to Heaven") assert(djd.play() == "Wuthering Heights") } }
Using intercept
, we can ensure that the play
method throws an exception when
we’d expect.