Start learning free

LESSON 566 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in Scala: Traits as Interfaces

Practice O(n) Read the full explanation

An app tells its users about news in different ways: some get an email, some get a text message, an SMS. The code that sends the news shouldn't care which kind each user picked. It only needs to know that each one can send a message. That promise is an interface.

Write:

  • the interface Notifier, with one method, send(message), which returns the text that would be sent;
  • EmailNotifier(address), a notifier whose send returns "To ", the address, ": " and the whole message;
  • SmsNotifier(phone), a notifier whose send returns "SMS ", the phone number, ": " and only the first 20 characters of the message (a message of 20 characters or fewer is sent whole);
  • a method notifyAll(notifiers, message) in the class Solution, which gets a list of notifiers of any kind and returns the list of the texts they send, in the same order.

notifyAll must work through the interface: it calls send on each notifier and never checks which kind of notifier it is.

In Scala, Notifier is a trait with an abstract send, and both notifier classes extend it.

The program creates the notifiers listed in the input, calls notifyAll once for each message, and prints each text it returns on its own line.

 

Example 1:

Input:
    notifiers: EmailNotifier("ana@mail.com"), SmsNotifier("0712345678")
    message: "Meeting at 5"
Output:
To ana@mail.com: Meeting at 5
SMS 0712345678: Meeting at 5
Explanation: The message has 12 characters, so the SMS sends all of it too.

Example 2:

Input:
    notifiers: SmsNotifier("5550100"), EmailNotifier("bob@work.org")
    message: "Your order has shipped today"
Output:
SMS 5550100: Your order has shipp
To bob@work.org: Your order has shipped today
Explanation: The message has 28 characters. The SMS keeps the first 20, "Your order has shipp";
the email sends all of it. The texts come in the order of the notifiers.

Example 3:

Input:
    notifiers: SmsNotifier("111"), SmsNotifier("222")
    messages: "Exactly twenty chars", "Exactly twenty chars!"
Output:
SMS 111: Exactly twenty chars
SMS 222: Exactly twenty chars
SMS 111: Exactly twenty chars
SMS 222: Exactly twenty chars
Explanation: notifyAll is called once for each message, with the same two notifiers.
The first message has exactly 20 characters and is sent whole. The second has 21, so its "!" is cut.

 

Constraints:

  • There are at most 50 notifiers. There can be none: then notifyAll returns an empty list.
  • A message has 1 to 100 characters: letters, digits, spaces and punctuation, with no space at its start or end.
  • Addresses and phone numbers have no spaces.
STEP 1 OF 7
    Ctrl + Enter to check

    SCALA · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Notifiers in Scala, explained

    Problem overview

    An app tells its users about news in different ways: some get an email, some get an SMS. The code that sends the news shouldn't care which kind each user picked. It only needs to know that each notifier can send a message. In Scala that promise is a trait.

    The lesson asks for four things:

    • the trait Notifier, with one method, send(message), that returns the text that would be sent;
    • EmailNotifier(address), whose send returns "To ", the address, ": " and the whole message;
    • SmsNotifier(phone), whose send returns "SMS ", the phone, ": " and only the first 20 characters of the message (a message of 20 characters or fewer is sent whole);
    • notifyAll(notifiers, message) in Solution, which returns the texts of all the notifiers, in the same order.

    With the message "Meeting at 5", the email notifier for ana@mail.com gives To ana@mail.com: Meeting at 5. The message has only 12 characters, so the SMS notifier sends all of it too.

    Approach

    A trait lists methods that its classes must provide. def send(message: String): String with no body is abstract, so EmailNotifier and SmsNotifier each define their own version. Code that holds a Notifier can call send without knowing the class, and Scala runs the version of the object's real class.

    That is why notifyAll is so short: it loops over the notifiers, calls send on each and collects the results. A third kind of notifier would work with it unchanged.

    Step by step

    1. Declare send in the trait, taking message: String and returning a String, with no body.
    2. In EmailNotifier, return "To " + address + ": " + message.
    3. In SmsNotifier, return "SMS " + phone + ": " plus the first 20 characters of the message, using message.substring(0, math.min(20, message.length)).
    4. In notifyAll, create an empty ArrayBuffer[String] called texts.
    5. Loop over notifiers and add notifier.send(message) to texts with +=.
    6. Return texts.toList.

    The solution in Scala

    trait Notifier {
        def send(message: String): String
    }
    
    class EmailNotifier(private val address: String) extends Notifier {
        override def send(message: String): String = {
            return "To " + address + ": " + message
        }
    }
    
    class SmsNotifier(private val phone: String) extends Notifier {
        override def send(message: String): String = {
            return "SMS " + phone + ": " + message.substring(0, math.min(20, message.length))
        }
    }
    
    class Solution {
        def notifyAll(notifiers: Array[Notifier], message: String): List[String] = {
            val texts = scala.collection.mutable.ArrayBuffer[String]()
            for (notifier <- notifiers) {
                texts += notifier.send(message)
            }
            return texts.toList
        }
    }
    

    Details worth knowing:

    • Private constructor values. private val address in the class header declares a field that only the class itself can read.
    • Why math.min. substring(0, 20) throws a StringIndexOutOfBoundsException on a string shorter than 20 characters. Taking the smaller of 20 and the length keeps short messages whole.
    • The result type. The buffer is mutable and is converted with toList, because the method returns an immutable List[String]. With no notifiers, the loop doesn't run and the result is an empty list.
    • The array of the trait type. The parameter is an Array[Notifier], so one array can hold both kinds of object.

    Trying it

    val solution = new Solution()
    val notifiers: Array[Notifier] = Array(new SmsNotifier("5550100"), new EmailNotifier("bob@work.org"))
    solution.notifyAll(notifiers, "Your order has shipped today").foreach(println)
    

    This prints:

    SMS 5550100: Your order has shipp
    To bob@work.org: Your order has shipped today
    

    The message has 28 characters. The SMS keeps the first 20, Your order has shipp, and the email sends everything. The texts come in the order of the notifiers. More inputs:

    • EmailNotifier("ana@mail.com") and SmsNotifier("0712345678") with "Meeting at 5" give To ana@mail.com: Meeting at 5 and SMS 0712345678: Meeting at 5.
    • Two SMS notifiers with "Exactly twenty chars" (exactly 20 characters) send it whole: SMS 111: Exactly twenty chars and SMS 222: Exactly twenty chars.
    • The same notifiers with "Exactly twenty chars!" (21 characters) cut the !, so the texts are identical to the ones above.
    • An empty array of notifiers gives List().

    Why a trait and not a base class

    The two notifiers share no data and no code. All they have in common is a promise: each can send a message and return the text. A trait describes exactly that. It names the method and leaves the work to the classes, so the sending code depends on the promise and not on any single notifier.

    A Scala class can extend only one parent class but can mix in several traits, so a trait leaves the class free to be part of other hierarchies as well. Here that freedom is not used, but it is a reason traits are the usual way to describe "can do this" in Scala.

    Following the loop

    For the notifiers SmsNotifier("5550100") and EmailNotifier("bob@work.org"), texts starts empty. The first pass calls the SMS version of send, which cuts the 28-character message to 20 characters, and adds the result. The second pass calls the email version, which keeps the whole message. The list then holds the two texts in the order of the array.

    Edge cases

    There can be no notifiers at all, and the method then returns an empty list. A message can be as short as one character, which is why the length check matters. A message of exactly 20 characters is the boundary: math.min(20, 20) is 20, so the whole message is kept. With 21 characters the minimum is 20 and the last character is cut. The array can also contain the same kind twice, as in the two SMS notifiers, and each one produces its own text.

    Common mistakes

    • Cutting at the wrong length. The limit is 20 characters, and a message of exactly 20 is sent whole.
    • Using substring(0, 20) alone. It fails on short messages.
    • Checking the class in notifyAll. Calling send on the trait type is enough, and an if on the kind would break when a new notifier is added.
    • Losing the order. Add the texts as you loop, and the result follows the order of the array.
    • Adding a space or a different separator. The email text is "To ", the address, ": ", the message, exactly.
    Keep going with Scala500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free