Start learning free

LESSON 566 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in Kotlin: Writing and Using an Interface

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 Kotlin, Notifier is an interface, and both notifier classes say : Notifier and override its send.

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

    KOTLIN · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Notifiers in Kotlin, explained

    Problem overview

    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 should not care which kind each user picked. It only needs to know that every notifier can send a message. That promise is an interface.

    You write four things:

    • the interface Notifier, with one method, send(message), which 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 number, ": " and only the first 20 characters of the message (a message of 20 characters or fewer goes whole);
    • notifyAll(notifiers, message) in the class Solution, which returns the list of texts the notifiers send, in the same order.

    For the notifiers EmailNotifier("ana@mail.com") and SmsNotifier("0712345678") and the message "Meeting at 5", the texts are To ana@mail.com: Meeting at 5 and SMS 0712345678: Meeting at 5.

    Approach

    An interface lists methods without bodies. A class that says : Notifier in its header agrees to provide every one of them, each marked override. Code that holds a Notifier can then call send without knowing the class behind it.

    notifyAll uses exactly that. It loops over an Array<Notifier>, calls send(message) on each element and collects the results. It never asks whether an element is an email or an SMS, so a third kind of notifier, say a push message, would work with it unchanged.

    Step by step

    1. Declare the interface with fun send(message: String): String and no body.
    2. In EmailNotifier, write override fun send returning "To " + address + ": " + message. The address is a private property declared in the class header.
    3. In SmsNotifier, return "SMS " + phone + ": " followed by the first 20 characters of the message. message.substring(0, minOf(20, message.length)) takes the smaller of 20 and the length, so a short message is sent whole.
    4. In notifyAll, create val texts = mutableListOf<String>(). It is a val because the variable never points to a different list, but the list itself can grow.
    5. Loop with for (notifier in notifiers) and add notifier.send(message) to texts.
    6. Return texts. With no notifiers the loop never runs and the result is an empty list.

    The solution in Kotlin

    interface Notifier {
        fun send(message: String): String
    }
    class EmailNotifier(private val address: String) : Notifier {
        override fun send(message: String): String {
            return "To " + address + ": " + message
        }
    }
    class SmsNotifier(private val phone: String) : Notifier {
        override fun send(message: String): String {
            return "SMS " + phone + ": " + message.substring(0, minOf(20, message.length))
        }
    }
    class Solution {
        fun notifyAll(notifiers: Array<Notifier>, message: String): List<String> {
            val texts = mutableListOf<String>()
            for (notifier in notifiers) {
                texts.add(notifier.send(message))
            }
            return texts
        }
    }
    

    Kotlin details:

    • Methods in an interface are abstract unless you give them a body, so send needs no abstract keyword. Implementing classes must use override; leaving it out gives the error 'send' hides member of supertype 'Notifier' and needs an 'override' modifier.
    • After the colon, an interface name has no parentheses (: Notifier), unlike a parent class (: Shipping()), because an interface has no constructor to call.
    • private val address in the header is both a constructor parameter and a property that only the class itself can read.
    • The function returns a List<String>, a read-only type. Inside, the value is a MutableList, which Kotlin lets you return as a List.

    Why minOf matters

    substring(0, 20) fails when the string is shorter than 20 characters. With the message "Hi" it stops the program with a StringIndexOutOfBoundsException, reporting that the range [0, 20) is out of bounds for length 2. The call with minOf(20, message.length) returns Hi for the same message. Kotlin also has message.take(20), which returns the whole string when it is shorter and prints Hi as well, so it would work in the same place.

    A run

    Called with the two notifiers of the first example and the message "Meeting at 5", the texts are:

    To ana@mail.com: Meeting at 5
    SMS 0712345678: Meeting at 5

    With SmsNotifier("5550100") and EmailNotifier("bob@work.org"), in that order, and the 28-character message "Your order has shipped today":

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

    The SMS keeps the first 20 characters, Your order has shipp, and the texts follow the order of the notifiers. With two SMS notifiers (phones 111 and 222) and the 20-character message "Exactly twenty chars", both texts keep the whole message. For the 21-character "Exactly twenty chars!", both texts lose the final !. Calling notifyAll with an empty array returns [].

    A third kind of notifier

    To check that notifyAll really does not care about the kind, add a class of your own in a file of your own:

    class PushNotifier(private val device: String) : Notifier {
        override fun send(message: String): String {
            return "Push to " + device + ": " + message
        }
    }
    

    Passing EmailNotifier("ana@mail.com") and PushNotifier("phone-1") with the message "Hello" to the same notifyAll prints To ana@mail.com: Hello and Push to phone-1: Hello. The function was not touched. This is the benefit of an interface: callers depend on the promise, not on the classes that keep it, and any class that keeps the promise can be passed in.

    Common mistakes

    • Cutting the message in the email too. Only the SMS is limited to 20 characters.
    • Using message.substring(0, 20) alone. It crashes for any short message.
    • Checking the class inside notifyAll. The loop should just call send.
    • Dropping override. The compiler rejects an implementation that lacks it.
    • Forgetting the space after the colon. The texts are compared exactly, so "To ana@mail.com:Hi" is a different text from "To ana@mail.com: Hi".
    • Changing the order. Add the texts as you go through the array, so they match the order of the notifiers.
    Keep going with Kotlin500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free