Lab: Default Interface Methods#


In this lab, we will delve into the concept of default interface methods. Our objective is to understand how to implement and utilize these methods effectively by working on a practical example: simple ciphers. By the end of this lab, we will have created a series of cipher classes that adhere to a common interface, ICipher.

Provided Code#

Carefully review the provided code. Notice that we have an interface named ICipher with a single method signature: char Encode(char). This interface serves as a contract for any cipher class we create, ensuring they all have a basic encoding functionality.

However, we can enhance its utility by adding a default method string Encode(string input). This method will iterate over each character in the input string, apply the Encode(char) method, and assemble the resulting characters into a new encoded string.

interface ICipher
    char Encode(char character);

    string Encode(string input)
        string output = "";
        foreach (char c in input)
            output += Encode(c);
        return output;

🤔 Reflection

How does implementing the Encode(string) method in the interface benefit all classes that implement ICipher? Would there be any drawbacks if each class had to implement its own version of this method?


Your task is to implement three different cipher classes that conform to the ICipher interface.

Step 1: Implement CaesarCipher#

Create a class CaesarCipher that implements ICipher. The constructor should accept an integer for the step length of the cipher. Implement the Encode(char) method to shift the character by the specified step length.

When you’re done, you should be able to run the following code:

ICipher cipher = new CaesarCipher(2);

Console.WriteLine(cipher.Encode("ABC xyz 123"));
CDE zab 123
ICipher cipher = new CaesarCipher(-1);

Console.WriteLine(cipher.Encode("ABC xyz 123"));
ZAB wxy 123

Step 2: Implement LeetCipher#

Create a class LeetCipher that also implements ICipher. In Encode(char), replace specific characters (like E, A, T) with numerals (like 3, 4, 7).

When you’re done, you should be able to run the following code:

ICipher cipher = new LeetCipher();

Console.WriteLine(cipher.Encode("Leet encode"));
1337 3ncod3

Step 3: Implement FlipFlopCipher#

This cipher alternates between two different ciphers for each character encoded. Create a class FlipFlopCipher with a constructor that takes two ICipher instances. Implement Encode(char) to alternate between these two ciphers.

When you’re done, you should be able to run the following code:

ICipher cipher = new FlipFlopCipher(
        new CaesarCipher(1),
        new CaesarCipher(-1));


Step 4: Reflect on the limitations of default interface methods#

Why does the following code fail to compile?

How can this limitation of default interface methods affect our design decisions?

CaesarCipher cipher = new CaesarCipher(2);
cipher.Encode("Hello world");
(2,15): error CS1503: Argument 1: cannot convert from 'string' to 'char'


Now that we’ve refactored our code, let’s tackle one more problem: CycleCipher. This cipher should take a list of ICipher instances and use each one in turn to encode characters in a string.

Implement CycleCipher that takes a list of ciphers in the constructor. The Encode(char) method should cycle through each cipher in the list for successive characters.

When you’re done you should be able to run the following code:

List<ICipher> ciphers = new List<ICipher>() {
        new CaesarCipher(1),
        new FlipFlopCipher(
                new CaesarCipher(1),
                new CaesarCipher(-1)),
        new LeetCipher(),
ICipher cipher = new CycleCipher(ciphers);


Bonus Challenge#

If you’re feeling on a roll, here’s a more difficult challenge for you 😎. Add the signature char Decode(char input) to the interface ICipher and implement it in all classes that implement the interface. Then add a default method to the interface ICipher with the signature string Decode(string decode).

Trust yourself. You’ve got this! 👊🌟