Start learning free

LESSON 566 OF 573 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in TypeScript: Interfaces and implements

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 TypeScript, Notifier is an interface, and both notifier classes say implements Notifier.

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 11
    Ctrl + Enter to check

    TYPESCRIPT · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Notifiers in TypeScript, explained

    Problem overview

    An app tells its users about news by email or by SMS. The code that sends the news shouldn't care which kind a user picked; it only needs to know that every notifier can send a message. That promise is an interface. You write:

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

    For an email notifier for ana@mail.com and an SMS notifier for 0712345678, the message "Meeting at 5" gives 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: program to the interface

    An interface lists method names and their types, with no bodies. A class that says implements Notifier promises to have those methods, and the compiler checks the promise. notifyAll then takes a Notifier[]: it calls send on each element and never asks which class the element is. A third kind of notifier, say a push notification, would work with notifyAll without any change to it.

    The loop visits each notifier once, so notifyAll takes time proportional to the number of notifiers (plus the cost of each send), and the texts it returns keep the order of the input.

    Step by step

    1. The interface. Inside Notifier, write only the signature send(message: string): string;, ending in a semicolon.
    2. The email's address. Declare a private field address of type string, so only the class's own methods use it, and store the constructor's parameter in this.address.
    3. The email's send. Join "To ", the address, ": " and the whole message.
    4. The SMS's phone. The same two pieces for phone: a private field, then the constructor storing it.
    5. The SMS's send. Join "SMS ", the phone, ": " and message.slice(0, 20). slice stops at the end of the string, so a short message is kept whole.
    6. notifyAll. Create an empty string[] with const, loop over the notifiers with for...of, push notifier.send(message) and return the array. With no notifiers the loop doesn't run and the empty array comes back.

    The solution in TypeScript

    interface Notifier {
        send(message: string): string;
    }
    
    class EmailNotifier implements Notifier {
        private address: string;
    
        constructor(address: string) {
            this.address = address;
        }
    
        send(message: string): string {
            return "To " + this.address + ": " + message;
        }
    }
    
    class SmsNotifier implements Notifier {
        private phone: string;
    
        constructor(phone: string) {
            this.phone = phone;
        }
    
        send(message: string): string {
            return "SMS " + this.phone + ": " + message.slice(0, 20);
        }
    }
    
    class Solution {
        notifyAll(notifiers: Notifier[], message: string): string[] {
            const texts: string[] = [];
            for (const notifier of notifiers) {
                texts.push(notifier.send(message));
            }
            return texts;
        }
    }
    

    TypeScript details worth knowing:

    • Interfaces exist only for the compiler. The compiled JavaScript has no trace of Notifier: it is a type, not a value. implements adds a check, and the classes work the same without it.
    • The check is real. If EmailNotifier says implements Notifier but has no send, the compiler reports Class 'EmailNotifier' incorrectly implements interface 'Notifier' and Property 'send' is missing in type 'EmailNotifier' but required in type 'Notifier'.
    • private is also a compile-time rule. Reading e.address from outside gives Property 'address' is private and only accessible within class 'EmailNotifier'. The compiler still writes the JavaScript, and there the field is an ordinary property, so the value would print.
    • slice and indexes. message.slice(0, 20) takes the characters at indexes 0 to 19. The end index is not included, which is why 20 gives 20 characters.
    • Strings are joined with +. When one side is a string, + concatenates. Every piece here is a string already.

    Seeing it run

    const s = new Solution();
    const ns: Notifier[] = [new EmailNotifier("ana@mail.com"), new SmsNotifier("0712345678")];
    console.log(s.notifyAll(ns, "Meeting at 5").join("\n"));
    console.log(s.notifyAll([new SmsNotifier("5550100"), new EmailNotifier("bob@work.org")], "Your order has shipped today").join("\n"));
    const two = [new SmsNotifier("111"), new SmsNotifier("222")];
    for (const m of ["Exactly twenty chars", "Exactly twenty chars!"]) console.log(s.notifyAll(two, m).join("\n"));
    console.log(s.notifyAll([], "Hi"));
    To ana@mail.com: Meeting at 5
    SMS 0712345678: Meeting at 5
    SMS 5550100: Your order has shipp
    To bob@work.org: Your order has shipped today
    SMS 111: Exactly twenty chars
    SMS 222: Exactly twenty chars
    SMS 111: Exactly twenty chars
    SMS 222: Exactly twenty chars
    []

    In the second call the message has 28 characters: the SMS keeps the first 20, Your order has shipp, and the email sends everything. The texts follow the order of the notifiers. In the third call, "Exactly twenty chars" has exactly 20 characters and is sent whole. The second message, with a ! at the end, has 21; the SMS cuts the !, so both messages produce the same SMS text.

    Adding a third kind

    To see why the interface pays off, write a third class in a file of your own:

    class PushNotifier implements Notifier {
        private device: string;
    
        constructor(device: string) {
            this.device = device;
        }
    
        send(message: string): string {
            return "Push " + this.device + ": " + message.slice(0, 10);
        }
    }
    console.log(new Solution().notifyAll([new PushNotifier("phone-1")], "A longer message here"));
    [ 'Push phone-1: A longer m' ]

    The lesson's notifyAll handles it unchanged. The compiler also guards the promise in both directions: a send that takes a number instead of a string, or that returns a number, is reported as not assignable to the interface's (message: string) => string.

    Edge cases and common mistakes

    • Checking the class. Writing if (notifier instanceof SmsNotifier) inside notifyAll defeats the interface. Call send and let each object do its own thing.
    • A message of exactly 20 characters. slice(0, 20) keeps all 20. Using 19 or 21 as the end index is the usual off-by-one.
    • Cutting the email. Only the SMS shortens the message. The email sends it whole.
    • No notifiers. The result is an empty array, not undefined.
    • Lost order. Push the texts in the loop so the output follows the input order.
    • A signature that doesn't match. If a class's send takes different parameters than the interface's, the compiler flags it in implements. Keep send(message: string): string exactly.
    • Missing separators. The texts are "To " + address + ": " + message: a forgotten space after the colon changes every line.
    Keep going with TypeScript500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free