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 methodsend(message)that returns the text that would be sent;EmailNotifier(address), whosesendreturns"To ", the address,": "and the whole message;SmsNotifier(phone), whosesendreturns"SMS ", the phone number,": "and only the first 20 characters of the message;notifyAll(notifiers, message)inSolution, 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
- The interface. In
Notifier, underpublic:, declarevirtual string send(string message) = 0;. The virtual destructor that follows is already in the starter code. - The email notifier's field. Declare a
privatestring address;. Only the class's own methods need it. - Its constructor. Store the parameter with
this->address = address;. - Its
send. Return"To " + address + ": " + message. - The SMS notifier works the same way with a private
string phone;andthis->phone = phone;. - Its
send. Return"SMS " + phone + ": " + message.substr(0, 20). The callsubstr(0, 20)takes up to 20 characters from the start, and a shorter string is returned whole. notifyAll. Declare an emptyvector<string> texts. Loop over the notifiers with a range-basedfor, and pushnotifier->send(message)intotexts. Returntextsafter 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 withnew, and the vector holds only their addresses. overrideasks the compiler to check that the method matches the interface'ssend. If it doesn't, the build fails instead of quietly creating a second, unrelated method.- The virtual destructor lets
deleteon aNotifier*free anEmailNotifieror anSmsNotifiercorrectly. - No notifiers. The loop doesn't run and the empty vector is returned. Calling
notifyAllwith 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
Notifierdirectly.Notifier n;doesn't compile: the compiler says it cannot declare the variable to be of abstract typeNotifier, becausesendis pure virtual. - A header that doesn't match. If an override takes
const string& messagewhile the interface takesstring message, the compiler reports that the method is markedoverridebut 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
| Notifiers | Message | Texts |
|---|---|---|
Email ana@mail.com, SMS 0712345678 | Meeting at 5 | To ana@mail.com: Meeting at 5, SMS 0712345678: Meeting at 5 |
SMS 111, SMS 222 | Exactly twenty chars! | SMS 111: Exactly twenty chars, SMS 222: Exactly twenty chars |
| none | Hi | an empty list |
