Problem overview
An app tells users about news by email or by SMS. The code that sends the news shouldn't care which kind each user picked. It only needs to know that every notifier can send a message. In Java 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), whosesendreturns"To ", the address,": "and the whole message;SmsNotifier(phone), whosesendreturns"SMS ", the phone,": "and only the first 20 characters of the message (a message of 20 characters or fewer goes whole);notifyAll(notifiers, message)inSolution, which returns the list of the texts the notifiers send, in the same order as the notifiers.
With 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. The message has 12 characters, so the SMS isn't cut.
Interfaces in Java
An interface lists method headers without bodies. A class that says implements Notifier must provide a public send(String message) of exactly that shape, or the program doesn't compile. Methods in an interface are public automatically, so the class's version has to be declared public too. Leaving the modifier out is a compile error (attempting to assign weaker access privileges; was public), and so is leaving the method out (PushNotifier is not abstract and does not override abstract method send(String) in Notifier).
A variable of the interface type can hold any object whose class implements it. That lets notifyAll take a list of Notifier and call send without asking what kind of notifier each element is. Java runs the send of the object's real class. A new kind of notifier, for example a push notification, would work with notifyAll without any change to it.
Step by step
- Declare the interface method. Inside
Notifier, write only the headerString send(String message);ending in a semicolon. - Give
EmailNotifiera private fieldaddress, so only the class's own methods use it. - Store it in the constructor with
this.address = address;. The parameter and the field share a name, andthis.picks the field. - Write the email's
send:"To " + address + ": " + message, marked@Overrideso the compiler checks it matches the interface. - Do the same for
SmsNotifierwith a private fieldphoneand its constructor. - Cut the message for the SMS with
message.substring(0, Math.min(20, message.length())). - Write
notifyAll: create an emptyArrayList<>namedtexts, loop over the notifiers with a for-each loop whose variable has the interface typeNotifier, addnotifier.send(message)to the list, and return it after the loop.
The solution in Java
interface Notifier {
String send(String message);
}
class EmailNotifier implements Notifier {
private String address;
public EmailNotifier(String address) {
this.address = address;
}
@Override
public String send(String message) {
return "To " + address + ": " + message;
}
}
class SmsNotifier implements Notifier {
private String phone;
public SmsNotifier(String phone) {
this.phone = phone;
}
@Override
public String send(String message) {
return "SMS " + phone + ": " + message.substring(0, Math.min(20, message.length()));
}
}
class Solution {
public List<String> notifyAll(List<Notifier> notifiers, String message) {
List<String> texts = new ArrayList<>();
for (Notifier notifier : notifiers) {
texts.add(notifier.send(message));
}
return texts;
}
}
Why Math.min is needed
substring(0, 20) throws an exception when the string is shorter than 20 characters. For "Hi" it throws a StringIndexOutOfBoundsException. Taking the smaller of 20 and message.length() as the end index handles both cases: a long message is cut to 20 characters, and a short one is returned whole. The end index of substring is exclusive, so substring(0, 20) keeps exactly characters 0 to 19, which is 20 characters.
Using it
The list's type is List<Notifier>, the interface, so it can hold both classes side by side.
Here is notifyAll on two notifiers of different kinds, with a message of 28 characters:
List<Notifier> notifiers = new ArrayList<>();
notifiers.add(new EmailNotifier("ana@mail.com"));
notifiers.add(new SmsNotifier("5550100"));
List<String> texts = new Solution().notifyAll(notifiers, "Your order has shipped today");
System.out.println(texts);
[To ana@mail.com: Your order has shipped today, SMS 5550100: Your order has shipp]
The email keeps the whole message. The SMS keeps its first 20 characters, Your order has shipp, and the texts come back in the same order as the notifiers.
Calling it more than once
The same notifiers can be reused for several messages, because notifyAll keeps nothing between calls. With SmsNotifier("111") and SmsNotifier("222"), the 20-character message Exactly twenty chars produces SMS 111: Exactly twenty chars and SMS 222: Exactly twenty chars. The 21-character Exactly twenty chars! produces exactly the same two texts, because the ! is cut. The notifiers' fields (address and phone) are set once in the constructor and never change.
Complexity
notifyAll calls send once per notifier, and each send builds one short string (the message is at most 100 characters), so the time is linear in the number of notifiers, and the result list holds one text per notifier. With no notifiers, the loop doesn't run and the method returns an empty list, [].
Edge cases and common mistakes
- Exactly 20 characters.
"Exactly twenty chars"has 20 characters, so the SMS sends it whole."Exactly twenty chars!"has 21, so the SMS drops the!. - Shorter than 20. Without
Math.min,substringthrows an exception. - Forgetting
publiconsendin a class that implements the interface. The compiler rejects it because the interface's method is public and an implementation can't be less visible. - Checking the class. Using
instanceofinsidenotifyAlldefeats the purpose of the interface. Callsendand let each object answer. - Reordering the results. The texts must follow the order of the notifiers, which the for-each loop and
addgive you. - Cutting the email as well. Only the SMS is limited to 20 characters; the email sends the whole message.
- Forgetting the separators. The prefix is
"To "or"SMS ", then the address or phone, then": "with a space after the colon.
