11. Inheritance and Polymorphism

11.6. Example: The Cipher Class Hierarchy

Suppose we wish to design a collection of cipher classes, including a Caesar cipher and a transposition cipher. Because the basic operations

used in all forms of encryption are the same, both the Caesar class andProblem decomposition

the Transpose class will have methods to encrypt() and decrypt()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Figure 8.13: pher classes. implements


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

A hierarchy of ci- The Cipher class operations common


messages, where each message is assumed to be a string of words sepa- rated by spaces. These methods will take a String of words and trans- late each word using the encoding method that is appropriate for that ci- pher. Therefore, in addition to encrypt() and decrypt(), each cipher class will need polymorphic encode() and decode() methods, which take a single word and encode or decode it according to the rules of that particular cipher.

From a design perspective the encrypt() and decrypt() meth- ods will be the same for every class: They simply break the message into words and encode or decode each word. However, the encode() and decode() methods will be different for each different cipher. The Caesar.encode() method should replace each letter of a word with its substitute, whereas the Transpose.encode() method should rearrange the letters of the word. Given these considerations, how should we design this set of classes?

Because all of the various ciphers will have the same methods, it will be helpful to define a common Cipher superclass (Fig. 8.13). Cipher will encapsulate those features that the individual cipher classes have in common—the encrypt(), decrypt(), encode(), and decode() methods.

Some of these methods can be implemented in the Cipher class itself. For example, the encrypt() method should take a message in a String parameter, encode each word in the message, and return a String result. The following method definition will work for any cipher:

,,

 

to all ciphers. The Caesar and Transpose classes imple- ment functions unique to those kinds of ciphers.

 

 

 

 

 

 

 

 

 

 

Inheritance


J

This method creates a local StringBuffer variable, result, and uses StringTokenizer to break the original String into its component words. It uses the encode() method to encode the word, appending the result into result. The result is converted back into a String and returned as the encrypted translation of s, the original message.

If we define encrypt() in the superclass, it will be inherited by all of

Cipher’s subclasses. Thus, if we define Caesar and Transpose as

 

,,

 

J

instances of these classes will be able to use the encrypt() method.

On the other hand, the polymorphic encode() method cannot be implemented within Cipher. This is because unlike the encrypt() method, which is the same for every Cipher subclass, the encode() method will be different for every subclass. However, by declaring the

 

encode() method as abstract, we can leave its implementation up to the Cipher subclasses. Thus, within the Cipher class, we would define encode() and decode() as follows:

,,

 

 

J

These declarations within the Cipher class tell the compiler that these methods will be implemented in Cipher’s subclasses. By defining it as abstract, encode() can be used in the Cipher class, as it is within the encrypt() method.

Class Design: Caesar

Figure 8.14 provides the full definition of the Cipher class. The encode() and decode() methods are declared abstract. They are in- tended to be implemented by Cipher’s subclasses.

,,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

J

Figure 8.14: The abstract Cipher class.

 

Note again that encrypt() and decrypt(), which are implemented

in Cipher, invoke encode() and decode(), respectively, which are de-encode() and decode() are

 

clared in Cipher but implemented in Cipher’s subclasses. Java’s dy- namic binding mechanism will take care of invoking the appropriate im- plementation of encode() or decode(), depending on what type of ob- ject is involved. For example, if caesar and transpose are Caesar and


polymorphic

 

Transpose objects, respectively, then the following calls to encrypt()

will cause their respective encode() methods to be invoked:

,,

 

 

 

 

 

 

 

Method polymorphism


J

When caesar.encrypt() is called, it will in turn invoke caesar.en- code()—that is, it will call the encode() method implemented in the Caesar class. When transpose.encrypt() is invoked, it will in turn invoke transpose.encode(). In this way, each object can perform the encoding algorithm appropriate for its type of cipher.

Algorithm Design: Shifting Characters

The Caesar class is defined as an extension of Cipher (Fig. 8.15). The only methods implemented in Caesar are encode() and decode(). The encode() method takes a String parameter and returns a String result. It takes each character of its parameter (word.charAt(k)) and performs a Caesar shift on the character. Note how the shift is done:

 

,,

 

J

Recall from Chapter 5 that char data in Java are represented as 16-bit integers. This enables us to manipulate characters as numbers. Thus, to shift a character by 3, we simply add 3 to its integer representation.

,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Character conversions


Figure 8.15: The Caesar class.

 

For example, suppose that the character (ch) is h, which has an ASCII

 

code of 104 (see Table 5.13). We want to shift it by 3, giving k, which has a code of 107. In this case, we could simply add 3 to 104 to get the de- sired result. However, suppose that ch was the character y, which has an ASCII code of 121. If we simply add 3 in this case, we get 124, a code that corresponds to the symbol “—,” which is not our desired result. Instead, we want the shift in this case to “wrap around” to the beginning of the alphabet, so that y gets shifted into b. In order to accomplish this we need to do some modular arithmetic.

Let’s suppose the 26 characters a to z were numbered 0 through 25, so that a corresponds to 0, b to 1, and so on up to z as 25. If we take any number N and divide it (modulo 26), we would get a number between 0 and 25. Suppose, for example, y were numbered 24. Shifting it by 3 would give us 27, and 27 % 26 would give us 1, which corresponds to b. So, if the a to z were numbered 0 through 25, then we can shift any character within that range by using the following formula:

,,

 

J

To map a character in the range a to z onto the integers 0 to 25, we can simply subtract a from it:

,,

 

 

 

J

Finally, we simply map the numbers 0 through 25 back to the characters a

to z to complete the shift operation:

,,

 

 

 

J

Note the use here of the cast operator (char) to covert an integer into a

char.

To summarize, we can shift any character by 3 if we map it into the range 0 to 25, then add 3 to it mod 26, then map that result back into

the range a to z. Thus, shifting y would go as follows:Modular arithmetic

 

,,

 

 

 

 

 

 

 

J

Note that in decode() a reverse Caesar shift is done by shifting by 23, which is 26 3. If the original shift is 3, we can reverse that by shifting an additional 23. Together this gives a shift of 26, which will give us back our original string.

 

Class Design: Transpose

The Transpose class (Fig. 8.16) is structured the same as the Caesar class. It implements both the encode() and decode() methods. The key element here is the transpose operation, which in this case is a simple reversal of the letters in the word. Thus, “hello” becomes “olleh”. This is very easy to do when using the StringBuffer.reverse() method. The decode() method is even simpler, because all you need to do in this case is call encode(). Reversing the reverse of a string gives you back the original string.

 

 

,,

 

 

 

 

 

 

 

 

J

Figure 8.16: The Transpose class.

 

 

 

Testing and Debugging

 

Figure 8.17 provides a simple test program for testing Cipher and its sub- classes. It creates a Caesar cipher and a Transpose cipher and then en-

 

,,

 

 

 

 

 

 

 

 

 

 

 

 

 

J

Figure 8.17: The TestEncrypt class.

 

crypts and decrypts the same sentence using each cipher. If you run this program, it will produce the following output:

,,

 

 

 

 

 

 

J

 

SELF-STUDY EXERCISES

EXERCISE 8.11 Modify the Caesar class so that it will allow various sized shifts to be used. (Hint: Use an instance variable to represent the shift.)

EXERCISE 8.12 Modify Transpose.encode() so that it uses a rota- tion instead of a reversal. That is, a word like “hello” should be encoded as “ohell” with a rotation of one character.