Start learning free

LESSON 565 OF 572 · OBJECT-ORIENTED PROGRAMMING - CHALLENGES

Notifiers in C#: Interfaces and Polymorphism

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 INotifier, 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#, the interface is INotifier (C# starts interface names with an I), and both notifier classes say : INotifier. The methods are Send and NotifyAll.

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 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 INotifier with one method, Send(message);
    • 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;
    • NotifyAll(notifiers, message) in Solution, 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

    1. In INotifier, write only the header: string Send(string message);. Interface members are public automatically, and there is no body.
    2. In EmailNotifier, keep the address in a private string address field and store the constructor's parameter with this.address = address.
    3. Its Send joins "To ", the address, ": " and the message with +.
    4. SmsNotifier does the same with a phone field.
    5. Its Send cuts the message with message.Substring(0, Math.Min(20, message.Length)).
    6. In NotifyAll, create an empty List<string>, loop over the notifiers with foreach, add each notifier.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 public on Send.
    • Calling Substring without the Math.Min.
    • Checking the real class of each notifier inside NotifyAll instead of just calling Send.
    • 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.
    Keep going with C#500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free