An Interface Case Study


Before moving on, it will be useful to work through another example that uses an interface. In this section we will create an interface called ICipher, which specifies methods that support the encryption of strings. Using an interface for this task makes sense because it is possible to fully separate the “what” of encryption from the “how.”

The ICipher interface is shown here:

 // An encryption interface. public interface ICipher {   string encode(string str);   string decode(string str); }

The ICipher interface specifies two methods: encode( ), which is used to encode a string, and decode( ), which is used to decode a string. No other details are specified. This means that implementing classes are free to use any approach to encryption. For example, one class could encode a string based upon a user-specified key. A second could use password protection. A third could use a bit-manipulation cipher, and another could use a simple transposition code. No matter what approach to encryption is used, the interface to encoding and decoding a string is the same. Since there is no need to specify any part of the encryption implementation, an interface makes the logical choice to represent it.

Here are two classes that implement ICipher. The first is SimpleCipher, which encodes a string by shifting each character one position higher. For example, A becomes B, B becomes C, and so on. The second is BitCipher, which encodes a string by XORing each character with a 16-bit value that acts as a key.

 /* A simple implementation of ICipher that codes    a message by shifting each character 1 position    higher.  Thus, A becomes B, and so on. */ class SimpleCipher : ICipher {   // Return an encoded string given plaintext.   public string encode(string str) {     string ciphertext = "";     for(int i=0; i < str.Length; i++)       ciphertext = ciphertext + (char) (str[i] + 1);     return ciphertext;   }   // Return a decoded string given ciphertext.   public string decode(string str) {     string plaintext = "";     for(int i=0; i < str.Length; i++)       plaintext = plaintext + (char) (str[i] - 1);     return plaintext;   } } /* This implementation of ICipher uses bit    manipulations and key. */ class BitCipher : ICipher {   ushort key;      // Specify a key when constructing BitCiphers.   public BitCipher(ushort k) {     key = k;   }   // Return an encoded string given plaintext.   public string encode(string str) {     string ciphertext = "";     for(int i=0; i < str.Length; i++)       ciphertext = ciphertext + (char) (str[i] ^ key);     return ciphertext;   }   // Return a decoded string given ciphertext.   public string decode(string str) {     string plaintext = "";     for(int i=0; i < str.Length; i++)       plaintext = plaintext + (char) (str[i] ^ key);     return plaintext;   } }

As you can see, although SimpleCipher and BitCipher differ in their implementations, they both implement the ICipher interface. The following program demonstrates SimpleCipher and BitCipher:

 // Demonstrate ICipher. using System; class ICipherDemo {   public static void Main() {     ICipher ciphRef;     BitCipher bit = new BitCipher(27);     SimpleCipher sc = new SimpleCipher();     string plain;     string coded;     // first, ciphRef refers to the simple cipher     ciphRef = sc;     Console.WriteLine("Using simple cipher.");     plain = "testing";     coded = ciphRef.encode(plain);     Console.WriteLine("Cipher text: " + coded);     plain = ciphRef.decode(coded);     Console.WriteLine("Plain text: " + plain);     // now, let ciphRef refer to the bitwise cipher     ciphRef = bit;     Console.WriteLine("\nUsing bitwise cipher.");     plain = "testing";     coded = ciphRef.encode(plain);     Console.WriteLine("Cipher text: " + coded);     plain = ciphRef.decode(coded);     Console.WriteLine("Plain text: " + plain);   } }

The output is shown here:

 Using simple cipher. Cipher text: uftujoh Plain text: testing Using bitwise cipher. Cipher text: o~horu| Plain text: testing

One advantage to creating an encryption interface is that any class that implements the interface is accessed in the same way, no matter how the encryption is implemented. For example, consider the following program in which a class called UnlistedPhone stores unlisted telephone numbers in an encrypted format. The names and numbers are automatically decoded when needed.

 // Use ICipher. using System; // A class for storing unlisted telephone numbers. class UnlistedPhone {   string pri_name;   // supports name property   string pri_number; // supports number property   ICipher crypt; // reference to encryption object   public UnlistedPhone(string name, string number, ICipher c)   {     crypt = c; // store encryption object     pri_name = crypt.encode(name);     pri_number = crypt.encode(number);   }   public string Name {     get {       return crypt.decode(pri_name);     }     set {       pri_name = crypt.encode(value);     }   }   public string Number {     get {       return crypt.decode(pri_number);     }     set {       pri_number = crypt.encode(value);     }   } } // Demonstrate UnlistedPhone class UnlistedDemo {   public static void Main() {     UnlistedPhone phone1 =         new UnlistedPhone("Tom", "555-3456", new BitCipher(27));     UnlistedPhone phone2 =         new UnlistedPhone("Mary", "555-8891", new BitCipher(9));     Console.WriteLine("Unlisted number for " +                       phone1.Name + " is " +                       phone1.Number);     Console.WriteLine("Unlisted number for " +                       phone2.Name + " is " +                       phone2.Number);   } }

The output from the program is shown here:

 Unlisted number for Tom is 555-3456 Unlisted number for Mary is 555-8891

Look closely at how UnlistedPhone is implemented. Notice that UnlistedPhone contains three fields. The first two are the private variables that store the name and telephone number. The third is a reference to an ICipher object. When an UnlistedPhone object is constructed, it is passed three references. The first two are to the strings holding the name and telephone number. The third is to the encryption object that is used to encode the name and number. A reference to the encryption object is stored in crypt. Any type of encryption object is acceptable as long as it implements the ICipher interface. In this case, BitCipher is used. Thus, UnlistedPhone can call the encode( ) and decode( ) methods on a BitCipher object through the crypt reference.

Notice now how the Name and Number properties work. When a set operation occurs, the name or number is automatically encoded by calling encode( ) on the crypt object. When a get operation takes place, the name or number is automatically decoded by calling decode( ). Neither Name nor Number has any specific knowledge of the underlying encryption method. They simply access its functionality through its interface.

Since the encryption interface is standardized by ICipher, it is possible to change the encryption object without changing any of the inner workings of UnlistedPhone’s code. For example, the following version of UnlistedDemo uses SimpleCipher rather than BitCipher when constructing UnlistedPhone objects. The only change that takes place is in the encryption object passed to the constructor for UnlistedPhone.

 // This version uses SimpleCipher. class UnlistedDemo {   public static void Main() {     // now, use SimpleCipher rather than BitCipher     UnlistedPhone phone1 =         new UnlistedPhone("Tom", "555-3456", new SimpleCipher());     UnlistedPhone phone2 =         new UnlistedPhone("Mary", "555-8891", new SimpleCipher());     Console.WriteLine("Unlisted number for " +                       phone1.Name + " is " +                       phone1.Number);     Console.WriteLine("Unlisted number for " +                       phone2.Name + " is " +                       phone2.Number);   } }

As this program shows, since both BitCipher and SimpleCipher implement ICipher, either can be used to construct UnlistedPhone objects.

One last point: The implementation of UnlistedPhone also shows the power of accessing objects that implement interfaces through an interface reference. Because the encryption object is referenced through an ICipher reference variable, any object that implements the ICipher interface can be used. This enables the specific encryption implementation to be changed painlessly and safely without causing any of UnlistedPhone’s code to be changed. If UnlistedPhone had instead hard-coded a specific type of encryption object, such as BitCipher, for the data type of crypt, then UnlistedPhone’s code would need to be changed if a different encryption scheme was desired.




C# 2.0(c) The Complete Reference
C# 2.0: The Complete Reference (Complete Reference Series)
ISBN: 0072262095
EAN: 2147483647
Year: 2006
Pages: 300

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net