Scala Design Patterns.
上QQ阅读APP看书,第一时间看更新

Composing with self-types

In the previous subsection, we saw how we were forced to extend Connector in our Watch class in order to properly compile our code. There are cases where we might actually want to enforce a trait to be mixed into a class that also has another trait or multiple traits mixed into it. Let's imagine that we want to have an alarm that must be able to notify us, no matter what:

trait AlarmNotifier {
this: Notifier =>

def trigger(): String
}

In the preceding code, we've shown a self-type. The highlighted piece of code brings all the methods of Notifier to the scope of our new trait and it also requires that any class that mixes in AlarmNotifier should also mix in Notifier. Otherwise, a compilation error will occur. Instead of this, we can use self and then refer to the Notifier methods inside AlarmNotifier by typing, for example, self. printNotification().

The following code is an example of how to use the new trait:

object SelfTypeWatchUser {
def main(args: Array[String]): Unit = {
// uncomment to see the self-type error.
// val watch = new Watch("alarm with notification", 1000L) with AlarmNotifier {
//}
val watch = new Watch("alarm with notification", 1000L) with AlarmNotifier with Notifier {
override def trigger(): String = "Alarm triggered."

override def clear(): Unit = {
System.out.println("Alarm cleared.")
}

override val notificationMessage: String = "The notification."
}

System.out.println(watch.trigger())
watch.printNotification()
System.out.println(s"The time is ${watch.getTime()}.")
watch.clear()
}
}

If we comment out the watch variable in the preceding code and uncomment the commented bit, we will see a compilation error that is raised due to the fact that we must also mix Notifier in.

In this subsection, we showed a simple use of self-types. One trait can require multiple other traits to be mixed in. In such cases, they are just separated with the with keyword. Self-types are a key part of the cake design pattern, which is used for dependency injection. We will see more interesting use cases later in this book.