Problem overview
An app tells users about news by email or by SMS. The code that sends the news should not care which one a user picked. It only needs to know that every notifier can send a message and give back the text that was sent. That promise is an interface.
You write:
- the interface
INotifierwith one method,Send(message); 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;NotifyAll(notifiers, message)inSolution, which returns the list of texts the notifiers send, in the same order.
With EmailNotifier("ana@mail.com") and SmsNotifier("0712345678"), the message "Meeting at 5" gives To ana@mail.com: Meeting at 5 and SMS 0712345678: Meeting at 5.
Approach: program against the interface
An interface lists methods without bodies. A class that says : INotifier promises to have them, and the compiler checks the promise. Interface names start with I by C# convention.
Then NotifyAll takes a List<INotifier> and calls Send on every element. It never asks whether an element is an email or an SMS notifier, so a third kind of notifier, written later, works with it unchanged.
Step by step
- In
INotifier, write only the header:string Send(string message);. Interface members are public automatically, and there is no body. - In
EmailNotifier, keep the address in aprivate string addressfield and store the constructor's parameter withthis.address = address. - Its
Sendjoins"To ", the address,": "and the message with+. SmsNotifierdoes the same with aphonefield.- Its
Sendcuts the message withmessage.Substring(0, Math.Min(20, message.Length)). - In
NotifyAll, create an emptyList<string>, loop over the notifiers withforeach, add eachnotifier.Send(message), and return the list.
The solution in C#
interface INotifier {
string Send(string message);
}
class EmailNotifier : INotifier {
private string address;
public EmailNotifier(string address) {
this.address = address;
}
public string Send(string message) {
return "To " + address + ": " + message;
}
}
class SmsNotifier : INotifier {
private string phone;
public SmsNotifier(string phone) {
this.phone = phone;
}
public string Send(string message) {
return "SMS " + phone + ": " + message.Substring(0, Math.Min(20, message.Length));
}
}
class Solution {
public List<string> NotifyAll(List<INotifier> notifiers, string message) {
List<string> texts = new List<string>();
foreach (INotifier notifier in notifiers) {
texts.Add(notifier.Send(message));
}
return texts;
}
}
Details worth knowing
Why Math.Min. Substring(0, 20) on a string shorter than 20 characters throws an ArgumentOutOfRangeException; running it on "Hello" did exactly that. Taking the smaller of 20 and the length means a short message is sent whole, and one of exactly 20 characters is also whole. A message of 21 characters loses its last character.
The methods must be public. A class member is private unless marked otherwise, and a private method cannot implement an interface member. The compiler reports that Send "cannot implement an interface member because it is not public" (CS0737).
Private fields. The address and phone are only used by the class's own Send, so they are private.
No notifiers. With an empty list the loop does not run and NotifyAll returns an empty list, whose Count is 0.
Using the classes
var solution = new Solution();
var notifiers = new List<INotifier> {
new SmsNotifier("5550100"),
new EmailNotifier("bob@work.org")
};
foreach (string text in solution.NotifyAll(notifiers, "Your order has shipped today")) {
Console.WriteLine(text);
}
SMS 5550100: Your order has shipp
To bob@work.org: Your order has shipped today
The texts come in the order of the notifiers. The SMS keeps "Your order has shipp", the first 20 characters. With the message "Exactly twenty chars" (20 characters) an SMS sends it whole, and with "Exactly twenty chars!" it sends the same text without the !.
A third kind of notifier
A new class that promises Send plugs into the same method without any change to it. A PushNotifier sending to a device name:
class PushNotifier : INotifier {
private string device;
public PushNotifier(string device) { this.device = device; }
public string Send(string message) { return "Push " + device + ": " + message; }
}
Used with an email, a push and an SMS notifier and the 39-character message "Meeting at 5, room 12, bring the report", NotifyAll returns:
To ana@mail.com: Meeting at 5, room 12, bring the report
Push phone-1: Meeting at 5, room 12, bring the report
SMS 0712345678: Meeting at 5, room 1
Only the SMS text is cut, and it is cut by SmsNotifier itself, not by NotifyAll. Each class owns its own rule, and the method that loops knows none of them.
Interface or class?
An interface carries no code and no fields. It describes what a caller may rely on, and a class can implement several interfaces. Here the two notifiers share nothing except the promise to have Send, which is why an interface fits better than a parent class.
Common mistakes
- Forgetting
publiconSend. - Calling
Substringwithout theMath.Min. - Checking the real class of each notifier inside
NotifyAllinstead of just callingSend. - Returning a new list of the wrong order, or sorting it.
Complexity
NotifyAll calls Send once for each notifier, so with n notifiers it makes n calls. Each call builds a string of at most a few hundred characters, so the work is proportional to n and to the message length, which is at most 100 here.
What the compiler checks
If a class says : INotifier but its Send has a different parameter list, or no Send exists, the program does not compile. That is the point of an interface: the mistake shows up when you build, not later when a message fails to go out. The reverse is also true: a class with a Send method that does not name the interface is not an INotifier, and cannot be put in a List<INotifier>.
Key takeaways
- An interface lists method headers without bodies, and a class that implements it must supply them as public methods.
- Declaring variables and parameters with the interface type lets one method handle every implementation.
- Keep each class's own rule inside that class. The truncation to 20 characters belongs to the SMS notifier alone.
