Start learning free

LESSON 562 OF 569 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in JavaScript: Interfaces and Polymorphism

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.

JavaScript has no interface keyword: as in the Interfaces lesson, Notifier is a stub class whose send throws an error, and both notifiers extend it with their own 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 12
    Ctrl + Enter to check

    JAVASCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Notifiers in JavaScript, explained

    Problem overview

    An app informs users in different ways: some get an email, some get 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.

    The task has four parts:

    • 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;
    • the method notifyAll(notifiers, message) in the class Solution, which returns the array of texts the notifiers send, in the same order.

    With an email notifier for ana@mail.com, an SMS notifier for 0712345678 and the message Meeting at 5, the texts are To ana@mail.com: Meeting at 5 and SMS 0712345678: Meeting at 5. The message has 12 characters, so the SMS sends all of it.

    Approach: depend on the interface

    notifyAll must work through the interface. It calls send on each notifier and never asks which kind it is. A new kind of notifier, such as a chat message, then needs a new class and no change to notifyAll.

    JavaScript has no interface keyword. The usual stand-in is a stub class: Notifier has a send that throws an error, and each real notifier extends it and replaces send. A class that forgets to replace it fails loudly when it is used, instead of quietly sending nothing.

    Step by step

    1. Write the stub. Notifier has send(message), whose body throws a new Error with the text "Not implemented".
    2. Declare #address in EmailNotifier. The # makes the field private: only the class's own methods can read it.
    3. Call super() in the constructor. A class that extends another must run the parent's constructor before it uses this. Notifier has no constructor of its own, so super() takes no arguments.
    4. Store the address in this.#address, so each email notifier remembers its own.
    5. Write EmailNotifier.send. Join "To ", the address, ": " and the message with +.
    6. Do the same for SmsNotifier. Declare #phone, call super(), store the phone. Its send uses message.slice(0, 20) instead of the whole message.
    7. Write notifyAll. Create an empty array texts, loop over the notifiers with for...of, push notifier.send(message) for each, and return the array.

    The solution in JavaScript

    class Notifier {
        send(message) { throw new Error("Not implemented"); }
    }
    
    class EmailNotifier extends Notifier {
        #address;
    
        constructor(address) {
            super();
            this.#address = address;
        }
    
        send(message) {
            return "To " + this.#address + ": " + message;
        }
    }
    
    class SmsNotifier extends Notifier {
        #phone;
    
        constructor(phone) {
            super();
            this.#phone = phone;
        }
    
        send(message) {
            return "SMS " + this.#phone + ": " + message.slice(0, 20);
        }
    }
    
    class Solution {
        notifyAll(notifiers, message) {
            let texts = [];
            for (let notifier of notifiers) {
                texts.push(notifier.send(message));
            }
            return texts;
        }
    }
    

    slice and the 20-character limit

    message.slice(0, 20) returns the characters from index 0 up to, but not including, index 20. If the message is shorter, slice stops at the end of the string and returns all of it, so no length check is needed. A 20-character message is sent whole, and a 21-character message loses its last character.

    Private fields

    A field written #address exists only inside the class body. Outside it, notifier.address is undefined, and writing notifier.#address is a syntax error. Callers can only use send, which is what an interface promises.

    Using the classes

    const solution = new Solution();
    const notifiers = [new SmsNotifier("5550100"), new EmailNotifier("bob@work.org")];
    console.log(solution.notifyAll(notifiers, "Your order has shipped today"));

    The result is an array with the texts in the order of the notifiers:

    [
      '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. Calling notifyAll again with the same two SMS notifiers 111 and 222 and the 21-character message Exactly twenty chars! cuts the !:

    [ 'SMS 111: Exactly twenty chars', 'SMS 222: Exactly twenty chars' ]

    With an empty array of notifiers, notifyAll([], "Hi") returns [].

    Why an interface helps

    Imagine notifyAll without the interface. It would need a branch for each kind: if the notifier is an email one, build the email text, if it is an SMS one, build the SMS text. Every new channel would mean editing that method and risking a bug in the channels that already work. With the interface, the knowledge about each channel stays inside its own class, and notifyAll is a plain loop that asks for the text and collects it.

    The same loop also handles the mix of kinds in one array and any number of notifiers, including a single one. Each object keeps its own address or phone number, so two SMS notifiers in the same array send to different numbers with the same code.

    Edge cases and common mistakes

    • No notifiers. The loop never runs, so the empty array texts goes back.
    • Forgetting super(). Using this in a subclass constructor before super() throws a ReferenceError.
    • Cutting the email too. Only the SMS shortens the message. The email sends it whole.
    • Checking the kind. An if (notifier instanceof SmsNotifier) inside notifyAll defeats the interface: each new kind would mean another branch.
    • Forgetting to override. A class such as class Push extends Notifier {} inherits the stub, and new Push().send("x") throws Not implemented.
    • Wrong spacing. The text is "To ", the address, then ": ", with exactly one space after the colon.

    Key takeaways

    • An interface is a promise of methods; in JavaScript a stub class whose methods throw plays that role.
    • Code written against the interface works with every class that keeps the promise.
    • slice(0, n) never fails on short strings.
    Keep going with JavaScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free