Start learning free

LESSON 561 OF 568 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in C++: Interfaces with Pure Virtual Methods

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 C++, Notifier is a class whose only method is pure virtual (= 0), as in the Interfaces lesson, and both notifiers inherit from it publicly. notifyAll gets the notifiers as a vector<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

    C++ · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

    Notifiers in C++, explained

    Problem overview

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

    You write:

    • Notifier, the interface, 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 number, ": " and only the first 20 characters of the message;
    • notifyAll(notifiers, message) in Solution, which returns the texts all the notifiers send, in the same order.

    For an email notifier with the address ana@mail.com and an SMS notifier with the number 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 keeps all of it.

    Approach: program against the interface

    C++ has no interface keyword. An interface is a class whose only method is pure virtual: it is declared with = 0 instead of a body. Every class that inherits from it must override that method before objects of the class can be created, and no plain Notifier can be created at all.

    The payoff is in notifyAll. It receives a vector<Notifier*>, pointers to the interface type, and calls send on each one. Since send is virtual, the call runs the version of the object's real class: the email text for an EmailNotifier, the SMS text for an SmsNotifier. The method never asks which kind it has. A third kind of notifier written later works with notifyAll unchanged.

    Step by step

    1. The interface. In Notifier, under public:, declare virtual string send(string message) = 0;. The virtual destructor that follows is already in the starter code.
    2. The email notifier's field. Declare a private string address;. Only the class's own methods need it.
    3. Its constructor. Store the parameter with this->address = address;.
    4. Its send. Return "To " + address + ": " + message.
    5. The SMS notifier works the same way with a private string phone; and this->phone = phone;.
    6. Its send. Return "SMS " + phone + ": " + message.substr(0, 20). The call substr(0, 20) takes up to 20 characters from the start, and a shorter string is returned whole.
    7. notifyAll. Declare an empty vector<string> texts. Loop over the notifiers with a range-based for, and push notifier->send(message) into texts. Return texts after the loop.

    The solution in C++

    class Notifier {
    public:
        virtual string send(string message) = 0;
        virtual ~Notifier() = default;
    };
    
    class EmailNotifier : public Notifier {
    private:
        string address;
    public:
        EmailNotifier(string address) {
            this->address = address;
        }
    
        string send(string message) override {
            return "To " + address + ": " + message;
        }
    };
    
    class SmsNotifier : public Notifier {
    private:
        string phone;
    public:
        SmsNotifier(string phone) {
            this->phone = phone;
        }
    
        string send(string message) override {
            return "SMS " + phone + ": " + message.substr(0, 20);
        }
    };
    
    class Solution {
    public:
        vector<string> notifyAll(vector<Notifier*> notifiers, string message) {
            vector<string> texts;
            for (Notifier* notifier : notifiers) {
                texts.push_back(notifier->send(message));
            }
            return texts;
        }
    };
    

    A run

    With the notifiers SmsNotifier("5550100") and EmailNotifier("bob@work.org") and the 28-character message "Your order has shipped today", notifyAll returns two texts, printed one per line:

    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 come in the order of the notifiers. With two SMS notifiers, "111" and "222", and the message "Exactly twenty chars", which has exactly 20 characters, both texts hold the whole message. Calling notifyAll a second time with "Exactly twenty chars!" (21 characters) cuts the ! off, so the SMS texts are the same as before.

    Details that matter

    • Pointers. The notifiers are passed as Notifier*, so methods are called with ->. The objects have to be created somewhere, for example with new, and the vector holds only their addresses.
    • override asks the compiler to check that the method matches the interface's send. If it doesn't, the build fails instead of quietly creating a second, unrelated method.
    • The virtual destructor lets delete on a Notifier* free an EmailNotifier or an SmsNotifier correctly.
    • No notifiers. The loop doesn't run and the empty vector is returned. Calling notifyAll with an empty list gives a vector of size 0.

    Complexity

    notifyAll calls send once for each of the n notifiers. Each call builds one string, which costs time proportional to the length of the text it builds. The message has at most 100 characters, so the time is O(n * m) for a message of length m, and the returned vector holds n texts, which is O(n * m) space as well.

    Edge cases and common mistakes

    • Creating a Notifier directly. Notifier n; doesn't compile: the compiler says it cannot declare the variable to be of abstract type Notifier, because send is pure virtual.
    • A header that doesn't match. If an override takes const string& message while the interface takes string message, the compiler reports that the method is marked override but does not override.
    • Cutting the email too. Only the SMS is limited to 20 characters. The email sends the whole message.
    • Counting the 20 wrongly. The limit counts characters of the message only, not the "SMS " and the phone number in front of it.
    • Checking the kind of notifier inside notifyAll. It works for two kinds but gives up the point of the interface: every new kind would need a new branch.

    As a check of that last point, a third class that inherits from Notifier and returns "Push: " + message from send works with the same notifyAll. With an EmailNotifier("a@b.c") and that class, the message "Hi" gives To a@b.c: Hi and Push: Hi.

    Testing

    NotifiersMessageTexts
    Email ana@mail.com, SMS 0712345678Meeting at 5To ana@mail.com: Meeting at 5, SMS 0712345678: Meeting at 5
    SMS 111, SMS 222Exactly twenty chars!SMS 111: Exactly twenty chars, SMS 222: Exactly twenty chars
    noneHian empty list
    Keep going with C++500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free