Skip to main content
Logo image

Java, Java, Java: Object-Oriented Problem Solving, 2024E

Section 8.8 Chapter Summary

Subsection 8.8.1 Principles of Object-Oriented Design

To conclude this chapter, it will be helpful to focus briefly on how the examples we’ve seen address the various object-oriented design (OOD) principles we set out at the beginning of the book.
  • Divide-and-Conquer Principle: Notice how all of the problems tackled in this chapter have been solved by dividing them into several classes, with each of the classes divided into separate methods. The very idea of a class hierarchy is an application of this principle.
  • Encapsulation Principle: The superclasses in our designs, Cipher and TwoPlayerGame, encapsulate those features of the class hierarchy that are shared by all objects in the hierarchy. The subclasses, CaesarCipher and OneRowNim, encapsulate features that make them distinctive with the class hierarchy. Interface Principle. The several Java interfaces we’ve designed, IPlayer, CLUIPlayableGame and UserInterface, specify clearly how various types of related objects will interact with each other through the methods contained in the interfaces. Clean interfaces make for clear communication among objects.
  • Information Hiding Principle: We have continued to make consistent use of the private and public qualifiers, and have now introduced the protected qualifier to extend this concept. The inheritance mechanism gives subclasses access to protected and public elements of their superclasses.
  • Generality Principle: As you move down a well-designed class hierarchy, you go from the more general to the more specific features of the objects involved. The abstract encode() method specifies the general form that encoding will take while the various implementations of this method in the subclasses provide the specializations necessary to distinguish, say, Caesar encoding from Transpose encoding. Similarly, the abstract makeAMove() method in the IPlayer interface provides a general format for a move in a two-player game, while its various implementations provide the specializations that distinguish one game from another.
  • Extensibility Principle:Overriding inherited methods and implementing abstract methods from either an abstract superclass or a Java interface provide several well-designed ways to extend the functionality in an existing class hierarchy. Extending a class is a form of specialization of the features inherited from the superclass.
  • Abstraction Principle: Designing a class hierarchy is an exercise in abstraction, as the more general features of the objects involved are moved into the superclasses. Similarly, designing a Java interface or an abstract superclass method is a form of abstraction, whereby the signature of the method is distinguished from its various implementations.
These, then, are some of the ways that the several examples we have considered and this chapter’s discussion have contributed to a deepening of our understanding of object-oriented design.

Subsection 8.8.2 Technical Terms

abstract method plaintext
actual type (dynamic type) polymorphic method
ciphertext polymorphism
class inheritance static binding (early binding)
cryptography static type (declared type)
dynamic binding (late binding) substitution cipher
interface transposition cipher
overloaded method

Subsection 8.8.3 Summary of Important Points

  • Inheritance is an object-oriented mechanism whereby subclasses inherit the public and protected instance variables and methods from their superclasses.
  • Dynamic binding (or late binding) is the mechanism by which a method call is bound to (associated with) the correct implementation of the method at run time. In Java, all method calls, except for final or private methods, are resolved using dynamic binding.
  • Static binding (or early binding) is the association of a method call with its corresponding implementation at compile time.
  • Polymorphism is an object-oriented language feature in which a method call can lead to different actions depending on the object on which it is invoked. A polymorphic method is a method signature that is given different implementation by different classes in a class hierarchy.
  • A static type is a variable’s declared type. A dynamic type, or actual type, is the type of object assigned to that variable at a given point in a running program.
  • An abstract method is a method definition that lacks an implementation. An abstract class is one that contains one or more abstract methods. An abstract class can be subclassed but not instantiated.
  • A Java interface is a class that contains only method signatures and (possibly) constant declarations, but no variables. An interface can be implemented by a class by providing implementations for all of its abstract methods.

Solutions 8.8.4 Solutions to Self-Study Exercises

8.2 Java’s Inheritance Mechanism
8.2.4 Polymorphism and Object-Oriented Design

Self-Study Exercises
8.2.4.1.
Solution.
Running the TestPrint with the default toString()produces:
56
TestPrint@6ff3c5b5
Overriding it as shown below produces:
56
Hello TestPrint
public class TestPrint {
   public static String NAME="TestPrint";
   /** Add a toString() method **/
    public String toString() {
        return "Hello" + NAME;
    }
    public static void main(String args[]) 
    {
        System.out.println(56);
        System.out.println(new TestPrint());
    }
}

8.2.5 Using super to Refer to the Superclass

Self-Study Exercises
8.2.5.1. B subclasses A.
Solution.
The second time you call a.method(), the variable a refers to a B so prints B:
ABB
8.2.5.2. B subclasses A, Part 2.
Solution.
The new implementation of B’s method() will invoke A’s version of the method before printing B, giving the ouput:
AABAB
8.2.5.3. Which are valid?
Solution.
All except four part C are valid

8.2.6 Inheritance and Constructors

Self-Study Exercises
8.2.6.1. Super Constructors.
Solution.
The output would be, AABABC. The constructors for B and C call the super class constructors.

8.3 Abstract Classes, Interfaces, and Polymorphism
8.3.1 Implementing an Abstract Method

Self-Study Exercises
8.3.1.1. Pig subclass.
Solution.
public class Pig extends Animal {
    public Pig() {
        kind = "pig";
    }
    public String speak() {
        return "oink";
    }
}
8.3.1.2. Pig talk.
Solution.
// A non-polymorphic design
public String talk(Animal a) {
  if (a instanceof Cow)
     return "I am a " + kind + " and I go " + a.moo();
   else if (a instanceof Cat)
     return "I am a " + kind + " and I go " + a.meow();
   else if (a instanceof Pig)
     return "I am a " + kind + " and I go " + a.oink();
   else
     return "I don't know what I am";
}

8.3.2 Implementing a Java Interface

Exercises
8.3.2.1. Speakable interface.
Solution.
The Pig class:
  class Pig extends Animal implements Speakable {
    public Pig() {
        kind = "pig";
    }
    public String speak() {
        return "oink";
    }
}
The main() method:
...
animal = new Pig();
System.out.println(animal.toString());
Will give the following output:
I am a cow and I go moo
I am a cat and I go meow
I am a pig and I go oink

8.4 Example: A Toggle Button
8.4.3 Swapping Algorithm

Self-Study Exercises
8.4.3.1. Swap Error.
Solution.

8.6 Example: The Cipher Class Hierarchy
8.6.5 Testing and Debugging

Self-Study Exercises
8.6.5.1. Caesar Shift.
Solution.
import java.util.*;
public class TestCipher {
   public static void main(String[] args)  {
     int shift = 7; 
     Caesar caesar = new Caesar(shift); 
     String text = "hello world";
     String encryptTxt = caesar.encrypt(text);
     String decryptTxt =  caesar.decrypt(encryptTxt);
     System.out.println(text + " encrypted with shift " + shift + " is " + encryptTxt);
     System.out.println(encryptTxt + " decrypted with shift " + shift + " is " + decryptTxt);
   }
}

abstract class Cipher {
  public String encrypt(String s) {
    StringBuffer result = new StringBuffer("");         // Use a StringBuffer
    StringTokenizer words = new StringTokenizer(s);     // Break s into its words
    while (words.hasMoreTokens()) {                     // For each word in s
      result.append(encode(words.nextToken()) + " ");   //  Encode it
    }
    return result.toString();                            // Return the result
  } // encrypt()

  public String decrypt(String s) {
    StringBuffer result = new StringBuffer("");        // Use a StringBuffer
    StringTokenizer words = new StringTokenizer(s);    // Break s into words
    while (words.hasMoreTokens()) {                    // For each word in s
      result.append(decode(words.nextToken()) + " ");  //  Decode it
    }
    return result.toString();                       // Return the decryption
  } // decrypt()

  public abstract String encode(String word);            // Abstract methods
  public abstract String decode(String word);    
} // Cipher

class Caesar extends Cipher {
  private int shift = 3;
  public Caesar(int shft) {
     shift = shft;
  }

  public String encode(String word) {
    StringBuffer result = new StringBuffer(); // Initialize a string buffer
    for (int k = 0; k < word.length(); k++) { // For each character in word
      char ch = word.charAt(k);               //  Get the character
      ch = (char)('a' + (ch -'a'+ shift) % 26);   //  Perform caesar shift
      result.append(ch);                   //  Append it to new string
    }
    return result.toString();              // Return the result as a string
  } // encode()

  public String decode(String word) {
    StringBuffer result = new StringBuffer(); // Initialize a string buffer
    for (int k = 0; k < word.length(); k++) { // For each character in word
    char ch = word.charAt(k);                 //  Get the character
       ch = (char)('a' + (ch - 'a' + (26 - shift)) % 26); //  Perform reverse shift
       result.append(ch);                     
       //  Append it to new string
    }
    return result.toString();            // Return the result as a string
  } // decode()
} // Caesar
8.6.5.2. Transpose Rotate.
Solution.

8.7 Case Study: A Two Player Game Hierarchy
8.7.11 Playing OneRowNim

Self-Study Exercises
8.7.11.1. Improved One Row Nim.
Solution.
The winning strategy would be to leave the opponent with 1 or 5 or 9 or 13 ... sticks. A NimPlayer class that plays the optimal OneRowNim game would be identical to the NimPlayerBad class except the move():int method would be replaced with the following implementation:
public int move() {
    int sticksLeft = game.getSticks();
    if (sticksLeft % (game.MAX_PICKUP + 1) != 1)
        return (sticksLeft - 1) % (game.MAX_PICKUP +1);
    else {
        int maxPickup = Math.min(game.MAX_PICKUP, sticksLeft);
         return 1 + (int)(Math.random() * maxPickup);
    }
 }
You might also want to modify main() to give the user the choice of which player to use as the computer:
 public static void main(String args[]) {
   KeyboardReader kb = new KeyboardReader();
   OneRowNim game = new OneRowNim();
   kb.prompt("How many computers are playing, 0, 1, or 2? ");
   int m = kb.getKeyboardInteger();
   for (int k = 0; k < m; k++) {
     kb.prompt("What type of player, " +
                 "NimPlayerBad = 1, or NimPlayer = 2 ? ");
     int choice = kb.getKeyboardInteger();
     if (choice == 1) {
       IPlayer computer = new NimPlayerBad(game);
       game.addComputerPlayer(computer);
     } else {
       IPlayer computer = new NimPlayer(game);
       game.addComputerPlayer(computer);
     }
   }
   game.play(kb);
} // main()
You have attempted of activities on this page.